mirror of
https://github.com/langchain-ai/langgraph.git
synced 2026-10-29 18:31:31 +01:00
Compare commits
100
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6920be192e | ||
|
|
312f0982bc | ||
|
|
153245145e | ||
|
|
c95abd88a1 | ||
|
|
a2b357bed5 | ||
|
|
7090d7e9a8 | ||
|
|
b3fa43e4a6 | ||
|
|
b1779cf348 | ||
|
|
26d18d3ca5 | ||
|
|
12052d7d26 | ||
|
|
e3a30a9b69 | ||
|
|
ff1370a9a5 | ||
|
|
679a7365da | ||
|
|
b2522ffe19 | ||
|
|
4212a795a0 | ||
|
|
517d67aa32 | ||
|
|
feaf14765a | ||
|
|
cc6063c729 | ||
|
|
013397042e | ||
|
|
9a775d9c9f | ||
|
|
2c945ceb68 | ||
|
|
39eabd0fb8 | ||
|
|
e5cc2e2044 | ||
|
|
f00c0515e7 | ||
|
|
d87c0d4d53 | ||
|
|
fb40a974c8 | ||
|
|
d63bfc6879 | ||
|
|
97dd30711a | ||
|
|
016a9c1936 | ||
|
|
a2d6837fba | ||
|
|
f5bb2a3b04 | ||
|
|
f807b73092 | ||
|
|
167405daf2 | ||
|
|
c6360e5408 | ||
|
|
f0505155a2 | ||
|
|
886df0fa86 | ||
|
|
3f1792d6ba | ||
|
|
9208052a94 | ||
|
|
7866bd2718 | ||
|
|
7c11325e23 | ||
|
|
d99dc7d81b | ||
|
|
973ad76a58 | ||
|
|
36e49eb190 | ||
|
|
66b9a7dee7 | ||
|
|
5494855ffa | ||
|
|
38d93a324c | ||
|
|
07c65321c1 | ||
|
|
1dbdd7df2e | ||
|
|
dab29ce094 | ||
|
|
0388534b9f | ||
|
|
81077e7c3a | ||
|
|
29a0042149 | ||
|
|
e9162e2516 | ||
|
|
0f6c001c25 | ||
|
|
7f26325c87 | ||
|
|
3c4ce3f945 | ||
|
|
84ef939bf4 | ||
|
|
bdc22ea127 | ||
|
|
5abbb79e1b | ||
|
|
0a5220aa07 | ||
|
|
970e68edcc | ||
|
|
da1a80e86d | ||
|
|
c4b240e0c2 | ||
|
|
c2052d11c2 | ||
|
|
dc0281b99c | ||
|
|
3a860ad537 | ||
|
|
7051bccc30 | ||
|
|
199e41b228 | ||
|
|
229a9e19a8 | ||
|
|
3c3a1a1f35 | ||
|
|
f11127648e | ||
|
|
29f833b1a7 | ||
|
|
7a3ea42743 | ||
|
|
a94902db8a | ||
|
|
9fd152ef3a | ||
|
|
03bc9ba6e6 | ||
|
|
7fe6f88876 | ||
|
|
1d88affd29 | ||
|
|
6906e12edb | ||
|
|
16bfa80b58 | ||
|
|
00964b18f6 | ||
|
|
0d5c6201d3 | ||
|
|
86d2847dab | ||
|
|
b3a4eaa967 | ||
|
|
87fc519ce7 | ||
|
|
ef3a1ee997 | ||
|
|
311e16dffd | ||
|
|
c83b8f6d04 | ||
|
|
62d3a85b07 | ||
|
|
810ae0ef51 | ||
|
|
2ff49d2200 | ||
|
|
d0567dc7be | ||
|
|
ea64ac5c07 | ||
|
|
090b53ccc1 | ||
|
|
0e872e7482 | ||
|
|
3ad966e057 | ||
|
|
89a0859928 | ||
|
|
433c382280 | ||
|
|
7352ab14a2 | ||
|
|
85a76912d3 |
No files matched your search
@@ -19,14 +19,19 @@ jobs:
|
||||
- "3.13"
|
||||
core-version:
|
||||
- "latest"
|
||||
ff-send-v2:
|
||||
- "false"
|
||||
include:
|
||||
- python-version: "3.11"
|
||||
core-version: ">=0.2.42,<0.3.0"
|
||||
- python-version: "3.11"
|
||||
core-version: "latest"
|
||||
ff-send-v2: "true"
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: libs/langgraph
|
||||
name: "test #${{ matrix.python-version }} (langchain-core: ${{ matrix.core-version }})"
|
||||
name: "test #${{ matrix.python-version }} (langchain-core: ${{ matrix.core-version }}, ff-send-v2: ${{ matrix.ff-send-v2 }})"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up Python ${{ matrix.python-version }} + Poetry ${{ env.POETRY_VERSION }}
|
||||
@@ -52,6 +57,8 @@ jobs:
|
||||
|
||||
- name: Run tests
|
||||
shell: bash
|
||||
env:
|
||||
LANGGRAPH_FF_SEND_V2: ${{ matrix.ff-send-v2 }}
|
||||
run: |
|
||||
make test
|
||||
|
||||
|
||||
@@ -36,10 +36,11 @@ NOTEBOOKS_NO_EXECUTION = [
|
||||
"docs/docs/tutorials/rag/langgraph_self_rag_local.ipynb",
|
||||
# this loads a massive dataset from gcp
|
||||
"docs/docs/tutorials/usaco/usaco.ipynb",
|
||||
# TODO: figure out why autogen notebook is not runnable (they are just hanging. possible due to code execution?)
|
||||
"docs/docs/how-tos/autogen-integration.ipynb",
|
||||
# TODO: need to update these notebooks to make sure they are runnable in CI
|
||||
"docs/docs/tutorials/storm/storm.ipynb", # issues only when running with VCR
|
||||
"docs/docs/tutorials/lats/lats.ipynb", # issues only when running with VCR
|
||||
"docs/docs/tutorials/multi_agent/hierarchical_agent_teams.ipynb", # taking a very long time to run
|
||||
"docs/docs/tutorials/rag/langgraph_crag.ipynb", # flakiness from tavily
|
||||
"docs/docs/tutorials/rag/langgraph_adaptive_rag.ipynb", # Cannot create a consistent method resolution error from VCR
|
||||
"docs/docs/how-tos/map-reduce.ipynb" # flakiness from structured output, only when running with VCR
|
||||
|
||||
@@ -1 +1 @@
|
||||
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 truncated
|
||||
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 truncated
|
||||
@@ -1 +1 @@
|
||||
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 truncated
|
||||
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
|
||||
+1
@@ -0,0 +1 @@
|
||||
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 truncated
|
||||
+1
@@ -0,0 +1 @@
|
||||
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 truncated
|
||||
+1
@@ -0,0 +1 @@
|
||||
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 truncated
|
||||
-1
@@ -1 +0,0 @@
|
||||
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 truncated
|
||||
@@ -1 +0,0 @@
|
||||
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 truncated
|
||||
+1
@@ -0,0 +1 @@
|
||||
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 truncated
|
||||
@@ -21,7 +21,7 @@ Install the proper packages:
|
||||
Ensure you have an API key, which you can create from the [LangSmith UI](https://smith.langchain.com) (Settings > API Keys). This is required to authenticate that you have LangGraph Cloud access. After you have saved the key to a safe place, place the following line in your `.env` file:
|
||||
|
||||
```python
|
||||
LANGCHAIN_API_KEY = *********
|
||||
LANGSMITH_API_KEY = *********
|
||||
```
|
||||
|
||||
## Start the API server
|
||||
@@ -54,7 +54,7 @@ You can either initialize by passing authentication or by setting an environment
|
||||
from langgraph_sdk import get_client
|
||||
|
||||
# only pass the url argument to get_client() if you changed the default port when calling langgraph up
|
||||
client = get_client(url=<DEPLOYMENT_URL>,api_key=<LANGCHAIN_API_KEY>)
|
||||
client = get_client(url=<DEPLOYMENT_URL>,api_key=<LANGSMITH_API_KEY>)
|
||||
# Using the graph deployed with the name "agent"
|
||||
assistant_id = "agent"
|
||||
thread = await client.threads.create()
|
||||
@@ -66,7 +66,7 @@ You can either initialize by passing authentication or by setting an environment
|
||||
import { Client } from "@langchain/langgraph-sdk";
|
||||
|
||||
// only set the apiUrl if you changed the default port when calling langgraph up
|
||||
const client = new Client({ apiUrl: <DEPLOYMENT_URL>, apiKey: <LANGCHAIN_API_KEY> });
|
||||
const client = new Client({ apiUrl: <DEPLOYMENT_URL>, apiKey: <LANGSMITH_API_KEY> });
|
||||
// Using the graph deployed with the name "agent"
|
||||
const assistantId = "agent";
|
||||
const thread = await client.threads.create();
|
||||
@@ -78,13 +78,13 @@ You can either initialize by passing authentication or by setting an environment
|
||||
curl --request POST \
|
||||
--url <DEPLOYMENT_URL>/threads \
|
||||
--header 'Content-Type: application/json'
|
||||
--header 'x-api-key: <LANGCHAIN_API_KEY>'
|
||||
--header 'x-api-key: <LANGSMITH_API_KEY>'
|
||||
```
|
||||
|
||||
|
||||
#### Initialize with environment variables
|
||||
|
||||
If you have a `LANGCHAIN_API_KEY` set in your environment, you do not need to explicitly pass authentication to the client
|
||||
If you have a `LANGSMITH_API_KEY` set in your environment, you do not need to explicitly pass authentication to the client
|
||||
|
||||
=== "Python"
|
||||
|
||||
@@ -154,7 +154,7 @@ Now we can invoke our graph to ensure it is working. Make sure to change the inp
|
||||
}
|
||||
```
|
||||
|
||||
=== "CURL"
|
||||
=== "CURL"
|
||||
|
||||
```bash
|
||||
curl --request POST \
|
||||
|
||||
@@ -94,6 +94,7 @@ Now we can start our two runs and join the second on euntil it has completed:
|
||||
assistant_id,
|
||||
input={"messages": [{"role": "user", "content": "what's the weather in sf?"}]},
|
||||
)
|
||||
# sleep a bit to get partial outputs from the first run
|
||||
await asyncio.sleep(2)
|
||||
run = await client.runs.create(
|
||||
thread["thread_id"],
|
||||
@@ -114,6 +115,7 @@ Now we can start our two runs and join the second on euntil it has completed:
|
||||
assistantId,
|
||||
{ input: { messages: [{ role: "human", content: "what's the weather in sf?" }] } }
|
||||
);
|
||||
// sleep a bit to get partial outputs from the first run
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
|
||||
let run = await client.runs.create(
|
||||
|
||||
@@ -95,7 +95,6 @@ Now let's run a thread with the multitask parameter set to "rollback":
|
||||
assistant_id,
|
||||
input={"messages": [{"role": "user", "content": "what's the weather in sf?"}]},
|
||||
)
|
||||
await asyncio.sleep(2)
|
||||
run = await client.runs.create(
|
||||
thread["thread_id"],
|
||||
assistant_id,
|
||||
@@ -115,7 +114,6 @@ Now let's run a thread with the multitask parameter set to "rollback":
|
||||
assistantId,
|
||||
{ input: { messages: [{ role: "human", content: "what's the weather in sf?" }] } }
|
||||
);
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
|
||||
let run = await client.runs.create(
|
||||
thread["thread_id"],
|
||||
@@ -139,7 +137,7 @@ Now let's run a thread with the multitask parameter set to "rollback":
|
||||
--data "{
|
||||
\"assistant_id\": \"agent\",
|
||||
\"input\": {\"messages\": [{\"role\": \"human\", \"content\": \"what\'s the weather in sf?\"}]},
|
||||
}" && sleep 2 && curl --request POST \
|
||||
}" && curl --request POST \
|
||||
--url <DEPLOY<ENT_URL>>/threads/<THREAD_ID>/runs \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data "{
|
||||
|
||||
@@ -2868,9 +2868,18 @@
|
||||
"description": "The cron schedule to execute this job on."
|
||||
},
|
||||
"assistant_id": {
|
||||
"type": "string",
|
||||
"format": "uuid",
|
||||
"title": "Assistant Id"
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"format": "uuid",
|
||||
"title": "Assistant Id"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"title": "Graph Id"
|
||||
}
|
||||
],
|
||||
"description": "The assistant ID or graph name to run. If using graph name, will default to the assistant automatically created from that graph by the server."
|
||||
},
|
||||
"input": {
|
||||
"anyOf": [
|
||||
@@ -3171,6 +3180,66 @@
|
||||
],
|
||||
"title": "Run"
|
||||
},
|
||||
"Send": {
|
||||
"type": "object",
|
||||
"title": "Send",
|
||||
"description": "A message to send to a node.",
|
||||
"properties": {
|
||||
"node": {
|
||||
"type": "string",
|
||||
"title": "Node",
|
||||
"description": "The node to send the message to."
|
||||
},
|
||||
"input": {
|
||||
"type": "object",
|
||||
"title": "Message",
|
||||
"description": "The message to send."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"node",
|
||||
"input"
|
||||
]
|
||||
},
|
||||
"Command": {
|
||||
"type": "object",
|
||||
"title": "Command",
|
||||
"description": "The command to run.",
|
||||
"properties": {
|
||||
"update": {
|
||||
"type": "object",
|
||||
"title": "Update",
|
||||
"description": "An update to the state."
|
||||
},
|
||||
"resume": {
|
||||
"type": [
|
||||
"object",
|
||||
"array",
|
||||
"number",
|
||||
"string",
|
||||
"null"
|
||||
],
|
||||
"title": "Resume",
|
||||
"description": "A value to pass to an interrupted node."
|
||||
},
|
||||
"send": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/Send"
|
||||
},
|
||||
{
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/Send"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"RunCreateStateful": {
|
||||
"properties": {
|
||||
"assistant_id": {
|
||||
@@ -3196,13 +3265,19 @@
|
||||
"input": {
|
||||
"anyOf": [
|
||||
{
|
||||
"items": {
|
||||
"type": "object"
|
||||
},
|
||||
"type": "array"
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"type": "object"
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"title": "Input",
|
||||
"description": "The input to the graph."
|
||||
},
|
||||
"command": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/Command"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
@@ -3405,13 +3480,19 @@
|
||||
"input": {
|
||||
"anyOf": [
|
||||
{
|
||||
"items": {
|
||||
"type": "object"
|
||||
},
|
||||
"type": "array"
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"type": "object"
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"title": "Input",
|
||||
"description": "The input to the graph."
|
||||
},
|
||||
"command": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/Command"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
|
||||
@@ -53,7 +53,7 @@ Example:
|
||||
}
|
||||
```
|
||||
|
||||
Example:
|
||||
Example with environment variables:
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -78,6 +78,37 @@ The base command for the LangGraph CLI is `langgraph`.
|
||||
langgraph [OPTIONS] COMMAND [ARGS]
|
||||
```
|
||||
|
||||
### `dev`
|
||||
|
||||
Run LangGraph API server in development mode with hot reloading and debugging capabilities. This lightweight server requires no Docker installation and is suitable for development and testing. State is persisted to a local directory.
|
||||
|
||||
**Installation**
|
||||
|
||||
This command requires the "inmem" extra to be installed:
|
||||
|
||||
```bash
|
||||
pip install -U "langgraph-cli[inmem]"
|
||||
```
|
||||
|
||||
**Usage**
|
||||
|
||||
```
|
||||
langgraph dev [OPTIONS]
|
||||
```
|
||||
|
||||
**Options**
|
||||
|
||||
| Option | Default | Description |
|
||||
|----------------------------|------------------|--------------------------------------------------------------------------------------------|
|
||||
| `-c, --config FILE` | `langgraph.json` | Path to configuration file declaring dependencies, graphs and environment variables |
|
||||
| `--host TEXT` | `127.0.0.1` | Host to bind the server to |
|
||||
| `--port INTEGER` | `2024` | Port to bind the server to |
|
||||
| `--no-reload` | | Disable auto-reload |
|
||||
| `--n-jobs-per-worker INTEGER` | | Number of jobs per worker. Default is 10 |
|
||||
| `--no-browser` | | Disable automatic browser opening |
|
||||
| `--debug-port INTEGER` | | Port for debugger to listen on |
|
||||
| `--help` | | Display command documentation |
|
||||
|
||||
### `build`
|
||||
|
||||
Build LangGraph Cloud API server Docker image.
|
||||
@@ -100,7 +131,7 @@ langgraph build [OPTIONS]
|
||||
|
||||
### `up`
|
||||
|
||||
Start langgraph API server. For local testing, requires a LangSmith API key with access to LangGraph Cloud closed beta. Requires a license key for production use.
|
||||
Start LangGraph API server. For local testing, requires a LangSmith API key with access to LangGraph Cloud closed beta. Requires a license key for production use.
|
||||
|
||||
**Usage**
|
||||
|
||||
@@ -120,8 +151,8 @@ langgraph up [OPTIONS]
|
||||
| `--verbose` | | Show more output from the server logs. |
|
||||
| `-c, --config FILE` | `langgraph.json` | Path to configuration file declaring dependencies, graphs and environment variables. |
|
||||
| `-d, --docker-compose FILE` | | Path to docker-compose.yml file with additional services to launch. |
|
||||
| `-p, --port INTEGER` | `8123` | Port to expose. Example: `langgraph test --port 8000` |
|
||||
| `--pull / --no-pull` | `pull` | Pull latest images. Use --no-pull for running the server with locally-built images. Example: `langgraph up --no-pull` |
|
||||
| `-p, --port INTEGER` | `8123` | Port to expose. Example: `langgraph up --port 8000` |
|
||||
| `--pull / --no-pull` | `pull` | Pull latest images. Use `--no-pull` for running the server with locally-built images. Example: `langgraph up --no-pull` |
|
||||
| `--recreate / --no-recreate` | `no-recreate` | Recreate containers even if their configuration and image haven't changed |
|
||||
| `--help` | | Display command documentation. |
|
||||
|
||||
@@ -148,9 +179,9 @@ Example:
|
||||
langgraph dockerfile -c langgraph.json Dockerfile
|
||||
```
|
||||
|
||||
Would generate something like the following:
|
||||
This generates a Dockerfile that looks similar to:
|
||||
|
||||
```text
|
||||
```dockerfile
|
||||
FROM langchain/langgraph-api:3.11
|
||||
|
||||
ADD ./pipconf.txt /pipconfig.txt
|
||||
@@ -170,6 +201,3 @@ RUN set -ex && \
|
||||
RUN PIP_CONFIG_FILE=/pipconfig.txt PYTHONDONTWRITEBYTECODE=1 pip install --no-cache-dir -c /api/constraints.txt -e /deps/*
|
||||
|
||||
ENV LANGSERVE_GRAPHS='{"agent": "/deps/__outer_graphs/src/agent.py:graph", "storm": "/deps/__outer_graphs/src/storm.py:graph"}'
|
||||
```
|
||||
|
||||
You can then customize, build images, push, and deploy from this file.
|
||||
@@ -15,10 +15,12 @@ If you do not want to use LangGraph Platform, we describe the options we have im
|
||||

|
||||
|
||||
## Reject
|
||||
|
||||
This is the simplest option, this just rejects any follow up runs and does not allow double texting.
|
||||
See the [how-to guide](../cloud/how-tos/reject_concurrent.md) 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](../cloud/how-tos/enqueue_concurrent.md) for configuring the enqueue double text option.
|
||||
|
||||
@@ -35,10 +37,6 @@ See the [how-to guide](../cloud/how-tos/interrupt_concurrent.md) for configuring
|
||||
|
||||
## 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.
|
||||
|
||||
This may create some weird states - for example, you may have two `User` messages in a row, with no `Asssitant` message in between them.
|
||||
You will need to make sure the LLM you are calling can handle that, or combine those into a single `User` message.
|
||||
This option interrupts the current execution AND rolls back all work done up until that point, including the original run input. It then sends the new user input in, basically as if it was the original input.
|
||||
|
||||
See the [how-to guide](../cloud/how-tos/rollback_concurrent.md) for configuring the rollback double text option.
|
||||
@@ -2,13 +2,13 @@
|
||||
|
||||
LLMs are extremely powerful, particularly when connected to other systems such as a retriever or APIs. This is why many LLM applications use a control flow of steps before and / or after LLM calls. As an example [RAG](https://github.com/langchain-ai/rag-from-scratch) performs retrieval of relevant documents to a question, and passes those documents to an LLM in order to ground the response. Often a control flow of steps before and / or after an LLM is called a "chain." Chains are a popular paradigm for programming with LLMs and offer a high degree of reliability; the same set of steps runs with each chain invocation.
|
||||
|
||||
However, we often want LLM systems that can pick their own control flow! This is one definition of an [agent](https://blog.langchain.dev/what-is-an-agent/): an agent is a system that uses an LLM to decide the control flow of an application. Unlike a chain, an agent given an LLM some degree of control over the sequence of steps in the application. Examples of using an LLM to decide the control of an application:
|
||||
However, we often want LLM systems that can pick their own control flow! This is one definition of an [agent](https://blog.langchain.dev/what-is-an-agent/): an agent is a system that uses an LLM to decide the control flow of an application. Unlike a chain, an agent gives an LLM some degree of control over the sequence of steps in the application. Examples of using an LLM to decide the control of an application:
|
||||
|
||||
- Using an LLM to route between two potential paths
|
||||
- Using an LLM to decide which of many tools to call
|
||||
- Using an LLM to decide whether the generated answer is sufficient or more work is need
|
||||
|
||||
There are many different types of [agent architectures](https://blog.langchain.dev/what-is-a-cognitive-architecture/) to consider, which given an LLM varying levels of control. On one extreme, a router allows an LLM to select a single step from a specified set of options and, on the other extreme, a fully autonomous long-running agent may have complete freedom to select any sequence of steps that it wants for a given problem.
|
||||
There are many different types of [agent architectures](https://blog.langchain.dev/what-is-a-cognitive-architecture/) to consider, which give an LLM varying levels of control. On one extreme, a router allows an LLM to select a single step from a specified set of options and, on the other extreme, a fully autonomous long-running agent may have complete freedom to select any sequence of steps that it wants for a given problem.
|
||||
|
||||

|
||||
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 144 KiB After Width: | Height: | Size: 141 KiB |
@@ -28,9 +28,28 @@ The CLI provides the following core functionality:
|
||||
|
||||
The `langgraph build` command builds a Docker image for the [LangGraph API server](./langgraph_server.md) that can be directly deployed.
|
||||
|
||||
### `dev`
|
||||
|
||||
!!! note "New in version 0.1.55"
|
||||
The `langgraph dev` command was introduced in langgraph-cli version 0.1.55.
|
||||
|
||||
The `langgraph dev` command starts a lightweight development server that requires no Docker installation. This server is ideal for rapid development and testing, with features like:
|
||||
|
||||
- Hot reloading: Changes to your code are automatically detected and reloaded
|
||||
- Debugger support: Attach your IDE's debugger for line-by-line debugging
|
||||
- In-memory state with local persistence: Server state is stored in memory for speed but persisted locally between restarts
|
||||
|
||||
To use this command, you need to install the CLI with the "inmem" extra:
|
||||
|
||||
```bash
|
||||
pip install -U "langgraph-cli[inmem]"
|
||||
```
|
||||
|
||||
**Note**: This command is intended for local development and testing only. It is not recommended for production use. Since it does not use Docker, we recommend using virtual environments to manage your project's dependencies.
|
||||
|
||||
### `up`
|
||||
|
||||
The `langgraph up` command starts an instance of the [LangGraph API server](./langgraph_server.md) locally. This requires docker to be installed and running locally. It also requires a LangSmith API key for local development or a license key for production use.
|
||||
The `langgraph up` command starts an instance of the [LangGraph API server](./langgraph_server.md) locally in a docker container. This requires thedocker server to be running locally. It also requires a LangSmith API key for local development or a license key for production use.
|
||||
|
||||
The server includes all API endpoints for your graph's runs, threads, assistants, etc. as well as the other services required to run your agent, including a managed database for checkpointing and storage.
|
||||
|
||||
|
||||
@@ -35,6 +35,16 @@ While in Beta, LangGraph Studio is available for free to all [LangSmith](https:/
|
||||
|
||||
If you have deployed your LangGraph application on LangGraph Platform (Cloud), you can access the studio as part of that
|
||||
|
||||
### Development server
|
||||
|
||||
LangGraph CLI also contains a command for running an in-memory development server that can be used to connect a local LangGraph app with the studio.
|
||||
See [instructions here](../cloud/reference/cli.md#dev) for more information.
|
||||
|
||||
The way this works is that it runs inside your local environment.
|
||||
It will spin up an in-memory, development server to deploy the graph.
|
||||
You can then connect to the studio via the Cloud hosted version of LangGraph Platform.
|
||||
To be clear, the web studio will connect to your locally running server - your agent is still running locally and never leaves your device.
|
||||
|
||||
## Studio FAQs
|
||||
|
||||
### Why is my project failing to start?
|
||||
|
||||
@@ -391,7 +391,7 @@ Read [this how-to](https://langchain-ai.github.io/langgraph/how-tos/recursion-li
|
||||
|
||||
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](./persistence.md) when using breakpoints. This is because your graph needs to be able to resume execution.
|
||||
You **MUST** use a [checkpointer](./persistence.md) when using breakpoints. This is because your graph needs to be able to resume execution.
|
||||
|
||||
In order to resume execution, you can just invoke your graph with `None` as the input.
|
||||
|
||||
|
||||
@@ -159,7 +159,7 @@ You must pass these when invoking the graph as part of the `configurable` portio
|
||||
# {"configurable": {"thread_id": "1", "checkpoint_id": "0c62ca34-ac19-445d-bbb0-5b4984975b2a"}} # also valid config
|
||||
|
||||
config = {"configurable": {"thread_id": "1"}}
|
||||
graph.invoke(inputs, config=config)
|
||||
graph.invoke(None, config=config)
|
||||
```
|
||||
|
||||
Importantly, LangGraph knows whether a particular checkpoint has been executed previously. If it has, LangGraph simply *re-plays* that particular step in the graph and does not re-execute the step. See this [how to guide on time-travel to learn more about replaying](../how-tos/human_in_the_loop/time-travel.ipynb).
|
||||
|
||||
@@ -6,22 +6,14 @@
|
||||
|
||||
Templates are open source reference applications designed to help you get started quickly when building with LangGraph. They provide working examples of common agentic workflows that can be customized to your needs.
|
||||
|
||||
Templates can be accessed via [LangGraph Studio](langgraph_studio.md), or cloned directly from Github. You can download LangGraph Studio and see available templates [here](https://studio.langchain.com/).
|
||||
Templates can be accessed via [LangGraph Studio (macOS only)](langgraph_studio.md), or cloned directly from Github. You can download LangGraph Studio and see available templates [here](https://studio.langchain.com/).
|
||||
|
||||
## Available templates
|
||||
|
||||
- **New LangGraph Project**: A simple, minimal chatbot with memory.
|
||||
- [Python](https://github.com/langchain-ai/new-langgraph-project)
|
||||
- [JS/TS](https://github.com/langchain-ai/new-langgraphjs-project)
|
||||
- **ReAct Agent**: A simple agent that can be flexibly extended to many tools.
|
||||
- [Python](https://github.com/langchain-ai/react-agent)
|
||||
- [JS/TS](https://github.com/langchain-ai/react-agent-js)
|
||||
- **Memory Agent**: A ReAct-style agent with an additional tool to store memories for use across conversational threads.
|
||||
- [Python](https://github.com/langchain-ai/memory-agent)
|
||||
- [JS/TS](https://github.com/langchain-ai/memory-agent-js)
|
||||
- **Retrieval Agent**: An agent that includes a retrieval-based question-answering system.
|
||||
- [Python](https://github.com/langchain-ai/retrieval-agent-template)
|
||||
- [JS/TS](https://github.com/langchain-ai/retrieval-agent-template-js)
|
||||
- **Data-enrichment Agent**: An agent that performs web searches and organizes its findings into a structured format.
|
||||
- [Python](https://github.com/langchain-ai/data-enrichment)
|
||||
- [JS/TS](https://github.com/langchain-ai/data-enrichment-js)
|
||||
| Template | Description | Python | JS/TS |
|
||||
|---------------------------|------------------------------------------------------------------------------------------|------------------------------------------------------------------|---------------------------------------------------------------------|
|
||||
| **New LangGraph Project** | A simple, minimal chatbot with memory. | [Repo](https://github.com/langchain-ai/new-langgraph-project) | [Repo](https://github.com/langchain-ai/new-langgraphjs-project) |
|
||||
| **ReAct Agent** | A simple agent that can be flexibly extended to many tools. | [Repo](https://github.com/langchain-ai/react-agent) | [Repo](https://github.com/langchain-ai/react-agent-js) |
|
||||
| **Memory Agent** | A ReAct-style agent with an additional tool to store memories for use across threads. | [Repo](https://github.com/langchain-ai/memory-agent) | [Repo](https://github.com/langchain-ai/memory-agent-js) |
|
||||
| **Retrieval Agent** | An agent that includes a retrieval-based question-answering system. | [Repo](https://github.com/langchain-ai/retrieval-agent-template) | [Repo](https://github.com/langchain-ai/retrieval-agent-template-js) |
|
||||
| **Data-Enrichment Agent** | An agent that performs web searches and organizes its findings into a structured format. | [Repo](https://github.com/langchain-ai/data-enrichment) | [Repo](https://github.com/langchain-ai/data-enrichment-js) |
|
||||
@@ -0,0 +1,332 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "100c0c81-6a9f-4ba1-b1a8-42aae82b7172",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# How to integrate LangGraph with AutoGen, CrewAI, and other frameworks\n",
|
||||
"\n",
|
||||
"LangGraph is a framework for building agentic and multi-agent applications. This includes integrating with other agent frameworks.\n",
|
||||
"\n",
|
||||
"This guides shows how to integrate LangGraph with other frameworks. The framework we show off integrating with is AutoGen, but this can easily be done with other frameworks.\n",
|
||||
"\n",
|
||||
"At a high level, the way this works is by wrapping the other agent inside a LangGraph node. LangGraph nodes can be anything - arbitrary code. This makes it easy to define an AutoGen (or CrewAI, or LlamaIndex, or other framework) agent and then reference it inside your graph. This allows you to create multi-agent systems where some of the sub-agents are actually defined in other frameworks."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "b189ceb2-132b-4c7b-81b4-c7b8b062f833",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Setup"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "62417d3a-94f9-4a52-9962-12639d714966",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"%pip install autogen bs4 langgraph langchain-openai langchain-community"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "d46da41d-0a71-4654-aec8-9e6ad8765236",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import getpass\n",
|
||||
"import os\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def _set_env(var: str):\n",
|
||||
" if not os.environ.get(var):\n",
|
||||
" os.environ[var] = getpass.getpass(f\"{var}: \")\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"_set_env(\"OPENAI_API_KEY\")\n",
|
||||
"_set_env(\"TAVILY_API_KEY\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "1926bbc3-6b06-41e0-9604-860a2bbf8fa3",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Define AutoGen agent\n",
|
||||
"\n",
|
||||
"Here we define our AutoGen agent. From https://github.com/microsoft/autogen/blob/0.2/notebook/agentchat_web_info.ipynb"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "524de117-ff09-4b26-bfe8-a9f85a46ffd5",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import autogen\n",
|
||||
"import os\n",
|
||||
"\n",
|
||||
"config_list = [{\"model\": \"gpt-4o\", \"api_key\": os.environ[\"OPENAI_API_KEY\"]}]\n",
|
||||
"\n",
|
||||
"llm_config = {\n",
|
||||
" \"timeout\": 600,\n",
|
||||
" \"cache_seed\": 42,\n",
|
||||
" \"config_list\": config_list,\n",
|
||||
" \"temperature\": 0,\n",
|
||||
"}\n",
|
||||
"\n",
|
||||
"autogen_agent = autogen.AssistantAgent(\n",
|
||||
" name=\"assistant\",\n",
|
||||
" llm_config=llm_config,\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"user_proxy = autogen.UserProxyAgent(\n",
|
||||
" name=\"user_proxy\",\n",
|
||||
" human_input_mode=\"NEVER\",\n",
|
||||
" max_consecutive_auto_reply=10,\n",
|
||||
" is_termination_msg=lambda x: x.get(\"content\", \"\").rstrip().endswith(\"TERMINATE\"),\n",
|
||||
" code_execution_config={\n",
|
||||
" \"work_dir\": \"web\",\n",
|
||||
" \"use_docker\": False,\n",
|
||||
" }, # Please set use_docker=True if docker is available to run the generated code. Using docker is safer than running the generated code directly.\n",
|
||||
" llm_config=llm_config,\n",
|
||||
" system_message=\"Reply TERMINATE if the task has been solved at full satisfaction. Otherwise, reply CONTINUE, or the reason why the task is not solved yet.\",\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "8aa858e2-4acb-4f75-be20-b9ccbbcb5073",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"---"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "d6bc7b69-4a36-44dc-a501-7e17122cc385",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Define LangGraph agent\n",
|
||||
"\n",
|
||||
"We now define our LangGraph agent. We will create a simple ReAct-style agent with a web search tool"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "a0fcdaac-8fbe-4589-8e61-8092165356cd",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langgraph.graph import StateGraph, START, MessagesState\n",
|
||||
"from langgraph.prebuilt import ToolNode, create_react_agent\n",
|
||||
"from langchain_community.tools.tavily_search import TavilySearchResults\n",
|
||||
"from langchain_openai import ChatOpenAI\n",
|
||||
"from langchain_core.messages import HumanMessage, AIMessage\n",
|
||||
"\n",
|
||||
"model = ChatOpenAI(model=\"gpt-4o\")\n",
|
||||
"tools = [TavilySearchResults(max_results=1)]\n",
|
||||
"web_search_agent = create_react_agent(\n",
|
||||
" model, tools, state_modifier=\"You are an agent specializing in web search\"\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "dcc478f5-4a35-43f8-bf59-9cb71289cd00",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Create the multi-agent graph\n",
|
||||
"\n",
|
||||
"We will now create our multi-agent system combining the AutoGen agent with the LangGraph agent. We can do this by creating a graph that routes user query to the appropriate agent and executes the agent"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "d129e4e1-3766-429a-b806-cde3d8bc0469",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from typing import Literal, TypedDict\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class Route(TypedDict):\n",
|
||||
" \"\"\"Decide where to go next\"\"\"\n",
|
||||
"\n",
|
||||
" goto: Literal[\"web_search_assistant\", \"coding_assistant\"]\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def route(state: MessagesState) -> Literal[\"web_search_assistant\", \"coding_assistant\"]:\n",
|
||||
" messages = [\n",
|
||||
" {\n",
|
||||
" \"role\": \"system\",\n",
|
||||
" \"content\": \"Based on the conversation so far, decide who to call next: web search assistant or coding assistant.\",\n",
|
||||
" }\n",
|
||||
" ] + state[\"messages\"]\n",
|
||||
" response = model.with_structured_output(Route).invoke(messages)\n",
|
||||
" return response[\"goto\"]\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def call_autogen_agent(state: MessagesState):\n",
|
||||
" last_message = state[\"messages\"][-1]\n",
|
||||
" response = user_proxy.initiate_chat(autogen_agent, message=last_message.content)\n",
|
||||
" # get the final response from the agent\n",
|
||||
" content = response.chat_history[-1][\"content\"]\n",
|
||||
" return {\"messages\": AIMessage(content=content)}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"builder = StateGraph(MessagesState)\n",
|
||||
"builder.add_conditional_edges(START, route)\n",
|
||||
"builder.add_node(\"coding_assistant\", call_autogen_agent)\n",
|
||||
"builder.add_node(\"web_search_assistant\", web_search_agent)\n",
|
||||
"graph = builder.compile()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 14,
|
||||
"id": "c761fc05-e8b6-4905-a793-eb7522d20060",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"image/jpeg": "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 truncated
|
||||
"text/plain": [
|
||||
"<IPython.core.display.Image object>"
|
||||
]
|
||||
},
|
||||
"metadata": {},
|
||||
"output_type": "display_data"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from IPython.display import display, Image\n",
|
||||
"\n",
|
||||
"display(Image(graph.get_graph().draw_mermaid_png()))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "23d629c3-1d6b-40af-adf6-915e15657566",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Run the graph\n",
|
||||
"\n",
|
||||
"We can now run the graph. We can see in the examples below how we first route to the appropriate agent, then respond with the subagent."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 15,
|
||||
"id": "b528ddb9-ec12-433c-a174-33d94dc49d80",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"\u001b[33muser_proxy\u001b[0m (to assistant):\n",
|
||||
"\n",
|
||||
"Find numbers between 10 and 30 in fibonacci sequence\n",
|
||||
"\n",
|
||||
"--------------------------------------------------------------------------------\n",
|
||||
"\u001b[33massistant\u001b[0m (to user_proxy):\n",
|
||||
"\n",
|
||||
"To find numbers between 10 and 30 in the Fibonacci sequence, we can generate the Fibonacci sequence and check which numbers fall within this range. Here's a plan:\n",
|
||||
"\n",
|
||||
"1. Generate Fibonacci numbers starting from 0.\n",
|
||||
"2. Continue generating until the numbers exceed 30.\n",
|
||||
"3. Collect and print the numbers that are between 10 and 30.\n",
|
||||
"\n",
|
||||
"Let's implement this in Python:\n",
|
||||
"\n",
|
||||
"```python\n",
|
||||
"# filename: fibonacci_range.py\n",
|
||||
"\n",
|
||||
"def fibonacci_sequence():\n",
|
||||
" a, b = 0, 1\n",
|
||||
" while a <= 30:\n",
|
||||
" if 10 <= a <= 30:\n",
|
||||
" print(a)\n",
|
||||
" a, b = b, a + b\n",
|
||||
"\n",
|
||||
"fibonacci_sequence()\n",
|
||||
"```\n",
|
||||
"\n",
|
||||
"Save this code in a file named `fibonacci_range.py` and execute it. It will print the Fibonacci numbers between 10 and 30. TERMINATE\n",
|
||||
"\n",
|
||||
"--------------------------------------------------------------------------------\n",
|
||||
"{'coding_assistant': {'messages': AIMessage(content=\"To find numbers between 10 and 30 in the Fibonacci sequence, we can generate the Fibonacci sequence and check which numbers fall within this range. Here's a plan:\\n\\n1. Generate Fibonacci numbers starting from 0.\\n2. Continue generating until the numbers exceed 30.\\n3. Collect and print the numbers that are between 10 and 30.\\n\\nLet's implement this in Python:\\n\\n```python\\n# filename: fibonacci_range.py\\n\\ndef fibonacci_sequence():\\n a, b = 0, 1\\n while a <= 30:\\n if 10 <= a <= 30:\\n print(a)\\n a, b = b, a + b\\n\\nfibonacci_sequence()\\n```\\n\\nSave this code in a file named `fibonacci_range.py` and execute it. It will print the Fibonacci numbers between 10 and 30. TERMINATE\", additional_kwargs={}, response_metadata={}, id='e95a8aa1-5aa8-4ff2-ba74-2b2993ea0a5a')}}\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"for chunk in graph.stream(\n",
|
||||
" {\n",
|
||||
" \"messages\": [\n",
|
||||
" {\n",
|
||||
" \"role\": \"user\",\n",
|
||||
" \"content\": \"Find numbers between 10 and 30 in fibonacci sequence\",\n",
|
||||
" }\n",
|
||||
" ]\n",
|
||||
" }\n",
|
||||
"):\n",
|
||||
" print(chunk)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 16,
|
||||
"id": "b120f9ba-f640-482b-a457-1893d6db5543",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"(('web_search_assistant:d08ae326-b6b2-1749-e8ea-4d308f22d819',), {'agent': {'messages': [AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_wZ5w5uO733Cc4CvWbc4Axq5F', 'function': {'arguments': '{\"query\":\"current weather in New York City\"}', 'name': 'tavily_search_results_json'}, 'type': 'function'}], 'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 23, 'prompt_tokens': 96, 'total_tokens': 119, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-2024-08-06', 'system_fingerprint': 'fp_45cf54deae', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-0658af11-b90b-407c-a6a5-6a3b4a9f61e0-0', tool_calls=[{'name': 'tavily_search_results_json', 'args': {'query': 'current weather in New York City'}, 'id': 'call_wZ5w5uO733Cc4CvWbc4Axq5F', 'type': 'tool_call'}], usage_metadata={'input_tokens': 96, 'output_tokens': 23, 'total_tokens': 119, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}})]}})\n",
|
||||
"(('web_search_assistant:d08ae326-b6b2-1749-e8ea-4d308f22d819',), {'tools': {'messages': [ToolMessage(content='[{\"url\": \"https://www.weatherapi.com/\", \"content\": \"{\\'location\\': {\\'name\\': \\'New York\\', \\'region\\': \\'New York\\', \\'country\\': \\'United States of America\\', \\'lat\\': 40.714, \\'lon\\': -74.006, \\'tz_id\\': \\'America/New_York\\', \\'localtime_epoch\\': 1732021037, \\'localtime\\': \\'2024-11-19 07:57\\'}, \\'current\\': {\\'last_updated_epoch\\': 1732020300, \\'last_updated\\': \\'2024-11-19 07:45\\', \\'temp_c\\': 8.3, \\'temp_f\\': 46.9, \\'is_day\\': 1, \\'condition\\': {\\'text\\': \\'Sunny\\', \\'icon\\': \\'//cdn.weatherapi.com/weather/64x64/day/113.png\\', \\'code\\': 1000}, \\'wind_mph\\': 7.2, \\'wind_kph\\': 11.5, \\'wind_degree\\': 332, \\'wind_dir\\': \\'NNW\\', \\'pressure_mb\\': 1016.0, \\'pressure_in\\': 29.99, \\'precip_mm\\': 0.0, \\'precip_in\\': 0.0, \\'humidity\\': 60, \\'cloud\\': 0, \\'feelslike_c\\': 6.3, \\'feelslike_f\\': 43.4, \\'windchill_c\\': 4.3, \\'windchill_f\\': 39.8, \\'heatindex_c\\': 7.0, \\'heatindex_f\\': 44.5, \\'dewpoint_c\\': 2.7, \\'dewpoint_f\\': 36.8, \\'vis_km\\': 16.0, \\'vis_miles\\': 9.0, \\'uv\\': 0.0, \\'gust_mph\\': 10.0, \\'gust_kph\\': 16.2}}\"}]', name='tavily_search_results_json', id='e955ebe9-631f-4dd1-b0a4-ee6a3caeba9f', tool_call_id='call_wZ5w5uO733Cc4CvWbc4Axq5F', artifact={'query': 'current weather in New York City', 'follow_up_questions': None, 'answer': None, 'images': [], 'results': [{'title': 'Weather in New York City', 'url': 'https://www.weatherapi.com/', 'content': \"{'location': {'name': 'New York', 'region': 'New York', 'country': 'United States of America', 'lat': 40.714, 'lon': -74.006, 'tz_id': 'America/New_York', 'localtime_epoch': 1732021037, 'localtime': '2024-11-19 07:57'}, 'current': {'last_updated_epoch': 1732020300, 'last_updated': '2024-11-19 07:45', 'temp_c': 8.3, 'temp_f': 46.9, 'is_day': 1, 'condition': {'text': 'Sunny', 'icon': '//cdn.weatherapi.com/weather/64x64/day/113.png', 'code': 1000}, 'wind_mph': 7.2, 'wind_kph': 11.5, 'wind_degree': 332, 'wind_dir': 'NNW', 'pressure_mb': 1016.0, 'pressure_in': 29.99, 'precip_mm': 0.0, 'precip_in': 0.0, 'humidity': 60, 'cloud': 0, 'feelslike_c': 6.3, 'feelslike_f': 43.4, 'windchill_c': 4.3, 'windchill_f': 39.8, 'heatindex_c': 7.0, 'heatindex_f': 44.5, 'dewpoint_c': 2.7, 'dewpoint_f': 36.8, 'vis_km': 16.0, 'vis_miles': 9.0, 'uv': 0.0, 'gust_mph': 10.0, 'gust_kph': 16.2}}\", 'score': 0.9997275, 'raw_content': None}], 'response_time': 3.24})]}})\n",
|
||||
"(('web_search_assistant:d08ae326-b6b2-1749-e8ea-4d308f22d819',), {'agent': {'messages': [AIMessage(content='The current weather in New York City is sunny with a temperature of 8.3°C (46.9°F). The wind is coming from the north-northwest at 7.2 mph (11.5 kph), and the humidity level is 60%. The weather feels slightly cooler at 6.3°C (43.4°F) due to the wind chill.', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 79, 'prompt_tokens': 535, 'total_tokens': 614, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-2024-08-06', 'system_fingerprint': 'fp_159d8341cc', 'finish_reason': 'stop', 'logprobs': None}, id='run-43d275f1-aacb-44f4-bdd0-8233c3765699-0', usage_metadata={'input_tokens': 535, 'output_tokens': 79, 'total_tokens': 614, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}})]}})\n",
|
||||
"((), {'web_search_assistant': {'messages': [HumanMessage(content=\"what's the weather in nyc?\", additional_kwargs={}, response_metadata={}, id='756466d3-18ce-4b8e-b4fc-ee59932ce9f4'), AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_wZ5w5uO733Cc4CvWbc4Axq5F', 'function': {'arguments': '{\"query\":\"current weather in New York City\"}', 'name': 'tavily_search_results_json'}, 'type': 'function'}], 'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 23, 'prompt_tokens': 96, 'total_tokens': 119, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-2024-08-06', 'system_fingerprint': 'fp_45cf54deae', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-0658af11-b90b-407c-a6a5-6a3b4a9f61e0-0', tool_calls=[{'name': 'tavily_search_results_json', 'args': {'query': 'current weather in New York City'}, 'id': 'call_wZ5w5uO733Cc4CvWbc4Axq5F', 'type': 'tool_call'}], usage_metadata={'input_tokens': 96, 'output_tokens': 23, 'total_tokens': 119, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}}), ToolMessage(content='[{\"url\": \"https://www.weatherapi.com/\", \"content\": \"{\\'location\\': {\\'name\\': \\'New York\\', \\'region\\': \\'New York\\', \\'country\\': \\'United States of America\\', \\'lat\\': 40.714, \\'lon\\': -74.006, \\'tz_id\\': \\'America/New_York\\', \\'localtime_epoch\\': 1732021037, \\'localtime\\': \\'2024-11-19 07:57\\'}, \\'current\\': {\\'last_updated_epoch\\': 1732020300, \\'last_updated\\': \\'2024-11-19 07:45\\', \\'temp_c\\': 8.3, \\'temp_f\\': 46.9, \\'is_day\\': 1, \\'condition\\': {\\'text\\': \\'Sunny\\', \\'icon\\': \\'//cdn.weatherapi.com/weather/64x64/day/113.png\\', \\'code\\': 1000}, \\'wind_mph\\': 7.2, \\'wind_kph\\': 11.5, \\'wind_degree\\': 332, \\'wind_dir\\': \\'NNW\\', \\'pressure_mb\\': 1016.0, \\'pressure_in\\': 29.99, \\'precip_mm\\': 0.0, \\'precip_in\\': 0.0, \\'humidity\\': 60, \\'cloud\\': 0, \\'feelslike_c\\': 6.3, \\'feelslike_f\\': 43.4, \\'windchill_c\\': 4.3, \\'windchill_f\\': 39.8, \\'heatindex_c\\': 7.0, \\'heatindex_f\\': 44.5, \\'dewpoint_c\\': 2.7, \\'dewpoint_f\\': 36.8, \\'vis_km\\': 16.0, \\'vis_miles\\': 9.0, \\'uv\\': 0.0, \\'gust_mph\\': 10.0, \\'gust_kph\\': 16.2}}\"}]', name='tavily_search_results_json', id='e955ebe9-631f-4dd1-b0a4-ee6a3caeba9f', tool_call_id='call_wZ5w5uO733Cc4CvWbc4Axq5F', artifact={'query': 'current weather in New York City', 'follow_up_questions': None, 'answer': None, 'images': [], 'results': [{'title': 'Weather in New York City', 'url': 'https://www.weatherapi.com/', 'content': \"{'location': {'name': 'New York', 'region': 'New York', 'country': 'United States of America', 'lat': 40.714, 'lon': -74.006, 'tz_id': 'America/New_York', 'localtime_epoch': 1732021037, 'localtime': '2024-11-19 07:57'}, 'current': {'last_updated_epoch': 1732020300, 'last_updated': '2024-11-19 07:45', 'temp_c': 8.3, 'temp_f': 46.9, 'is_day': 1, 'condition': {'text': 'Sunny', 'icon': '//cdn.weatherapi.com/weather/64x64/day/113.png', 'code': 1000}, 'wind_mph': 7.2, 'wind_kph': 11.5, 'wind_degree': 332, 'wind_dir': 'NNW', 'pressure_mb': 1016.0, 'pressure_in': 29.99, 'precip_mm': 0.0, 'precip_in': 0.0, 'humidity': 60, 'cloud': 0, 'feelslike_c': 6.3, 'feelslike_f': 43.4, 'windchill_c': 4.3, 'windchill_f': 39.8, 'heatindex_c': 7.0, 'heatindex_f': 44.5, 'dewpoint_c': 2.7, 'dewpoint_f': 36.8, 'vis_km': 16.0, 'vis_miles': 9.0, 'uv': 0.0, 'gust_mph': 10.0, 'gust_kph': 16.2}}\", 'score': 0.9997275, 'raw_content': None}], 'response_time': 3.24}), AIMessage(content='The current weather in New York City is sunny with a temperature of 8.3°C (46.9°F). The wind is coming from the north-northwest at 7.2 mph (11.5 kph), and the humidity level is 60%. The weather feels slightly cooler at 6.3°C (43.4°F) due to the wind chill.', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 79, 'prompt_tokens': 535, 'total_tokens': 614, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-2024-08-06', 'system_fingerprint': 'fp_159d8341cc', 'finish_reason': 'stop', 'logprobs': None}, id='run-43d275f1-aacb-44f4-bdd0-8233c3765699-0', usage_metadata={'input_tokens': 535, 'output_tokens': 79, 'total_tokens': 614, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}})]}})\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"for chunk in graph.stream(\n",
|
||||
" {\"messages\": [{\"role\": \"user\", \"content\": \"what's the weather in nyc?\"}]},\n",
|
||||
" subgraphs=True,\n",
|
||||
"):\n",
|
||||
" print(chunk)"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.12.3"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "8381b6e0-29a6-48c5-b451-5d2549351249",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# How to use LangGraph Platform to deploy CrewAI, AutoGen, and other frameworks\n",
|
||||
"\n",
|
||||
"[LangGraph Platform](https://langchain-ai.github.io/langgraph/concepts/langgraph_platform/) provides infrastructure for deploying agents. This integrates seamlessly with LangGraph, but can also work with other frameworks. The way to make this work is to wrap the agent in a single LangGraph node, and have that be the entire graph.\n",
|
||||
"\n",
|
||||
"Doing so will allow you to deploy to LangGraph Platform, and allows you to get a lot of the [benefits](https://langchain-ai.github.io/langgraph/concepts/langgraph_platform/). You get horizontally scalable infrastructure, a task queue to handle bursty operations, a persistence layer to power short term memory, and long term memory support.\n",
|
||||
"\n",
|
||||
"In this guide we show how to do this with an AutoGen agent, but this method should work for agents defined in other frameworks like CrewAI, LlamaIndex, and others as well."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "1113cb16-b538-448c-924c-85731ce96ebd",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Setup"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 10,
|
||||
"id": "f05993fa-9d03-4f45-bc13-0a8d87260d86",
|
||||
"metadata": {
|
||||
"scrolled": true
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"%pip install autogen langgraph"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "f4e0ca12-1714-4776-a30a-9527e519799b",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import getpass\n",
|
||||
"import os\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def _set_env(var: str):\n",
|
||||
" if not os.environ.get(var):\n",
|
||||
" os.environ[var] = getpass.getpass(f\"{var}: \")\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"_set_env(\"OPENAI_API_KEY\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "1926bbc3-6b06-41e0-9604-860a2bbf8fa3",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Define autogen agent\n",
|
||||
"\n",
|
||||
"Here we define our AutoGen agent. From https://github.com/microsoft/autogen/blob/0.2/notebook/agentchat_web_info.ipynb"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "d4a14dc7-d565-4207-8788-525f85b9fb27",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import autogen\n",
|
||||
"import os\n",
|
||||
"\n",
|
||||
"config_list = [{\"model\": \"gpt-4o\", \"api_key\": os.environ[\"OPENAI_API_KEY\"]}]\n",
|
||||
"\n",
|
||||
"llm_config = {\n",
|
||||
" \"timeout\": 600,\n",
|
||||
" \"cache_seed\": 42,\n",
|
||||
" \"config_list\": config_list,\n",
|
||||
" \"temperature\": 0,\n",
|
||||
"}\n",
|
||||
"\n",
|
||||
"autogen_agent = autogen.AssistantAgent(\n",
|
||||
" name=\"assistant\",\n",
|
||||
" llm_config=llm_config,\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"user_proxy = autogen.UserProxyAgent(\n",
|
||||
" name=\"user_proxy\",\n",
|
||||
" human_input_mode=\"NEVER\",\n",
|
||||
" max_consecutive_auto_reply=10,\n",
|
||||
" is_termination_msg=lambda x: x.get(\"content\", \"\").rstrip().endswith(\"TERMINATE\"),\n",
|
||||
" code_execution_config={\n",
|
||||
" \"work_dir\": \"web\",\n",
|
||||
" \"use_docker\": False,\n",
|
||||
" }, # Please set use_docker=True if docker is available to run the generated code. Using docker is safer than running the generated code directly.\n",
|
||||
" llm_config=llm_config,\n",
|
||||
" system_message=\"Reply TERMINATE if the task has been solved at full satisfaction. Otherwise, reply CONTINUE, or the reason why the task is not solved yet.\",\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "b1170836-f23e-4e4c-ab83-ce791cd7fbd2",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Wrap in LangGraph\n",
|
||||
"\n",
|
||||
"We now wrap the AutoGen agent in a single LangGraph node, and make that the entire graph.\n",
|
||||
"The main thing this involves is defining an Input and Output schema for the node, which you would need to do if deploying this manually, so it's no extra work"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 11,
|
||||
"id": "7b417c16-ff4e-4d5c-a9a9-0aaeeef6ede5",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langgraph.graph import StateGraph, MessagesState\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def call_autogen_agent(state: MessagesState):\n",
|
||||
" last_message = state[\"messages\"][-1]\n",
|
||||
" response = user_proxy.initiate_chat(autogen_agent, message=last_message.content)\n",
|
||||
" # get the final response from the agent\n",
|
||||
" content = response.chat_history[-1][\"content\"]\n",
|
||||
" return {\"messages\": {\"role\": \"assistant\", \"content\": content}}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"graph = StateGraph(MessagesState)\n",
|
||||
"graph.add_node(call_autogen_agent)\n",
|
||||
"graph.set_entry_point(\"call_autogen_agent\")\n",
|
||||
"graph = graph.compile()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "f6a18377-ac29-478f-a76a-b213f1a3c85d",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Deploy with LangGraph Platform\n",
|
||||
"\n",
|
||||
"You can now deploy this as you normally would with LangGraph Platform. See [these instructions](https://langchain-ai.github.io/langgraph/concepts/deployment_options/) for more details."
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.12.3"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
@@ -345,7 +345,7 @@
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.11.4"
|
||||
"version": "3.11.1"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
|
||||
@@ -23,8 +23,8 @@ You will eventually need to pass in the following environment variables to the L
|
||||
|
||||
- `REDIS_URI`: Connection details to a Redis instance. Redis will be used as a pub-sub broker to enable streaming real time output from background runs.
|
||||
- `DATABASE_URI`: Postgres connection details. Postgres will be used to store assistants, threads, runs, persist thread state and long term memory, and to manage the state of the background task queue with 'exactly once' semantics.
|
||||
- `LANGSMITH_API_KEY`: (If using [Self-Hosted Lite]) LangSmith API key. This will be used to authenticate ONCE at server start up.
|
||||
- `LANGGRAPH_CLOUD_LICENSE_KEY`: (If using Self-Hosted Enterprise) LangGraph Platform license key. This will be used to authenticate ONCE at server start up.
|
||||
- `LANGSMITH_API_KEY`: (If using [Self-Hosted Lite](../concepts/deployment_options.md#self-hosted-lite)) LangSmith API key. This will be used to authenticate ONCE at server start up.
|
||||
- `LANGGRAPH_CLOUD_LICENSE_KEY`: (If using [Self-Hosted Enterprise](../concepts/deployment_options.md#self-hosted-enterprise)) LangGraph Platform license key. This will be used to authenticate ONCE at server start up.
|
||||
|
||||
|
||||
## Build the Docker Image
|
||||
@@ -70,7 +70,7 @@ If you want to run this quickly without setting up a separate Redis and Postgres
|
||||
* You need to replace `my-image` with the name of the image you built in the previous step (from `langgraph build`).
|
||||
and you should provide appropriate values for `REDIS_URI`, `DATABASE_URI`, and `LANGSMITH_API_KEY`.
|
||||
* If your application requires additional environment variables, you can pass them in a similar way.
|
||||
* If using Self-Hosted Enterprise, you must provide `LANGGRAPH_CLOUD_LICENSE_KEY` as an additional environment variable.
|
||||
* If using [Self-Hosted Enterprise](../concepts/deployment_options.md#self-hosted-enterprise), you must provide `LANGGRAPH_CLOUD_LICENSE_KEY` as an additional environment variable.
|
||||
|
||||
|
||||
### Using Docker Compose
|
||||
|
||||
@@ -103,6 +103,7 @@ These how-to guides show common patterns for tool calling with LangGraph:
|
||||
- [How to force function calling agent to structure output](react-agent-structured-output.ipynb)
|
||||
- [How to pass custom LangSmith run ID for graph runs](run-id-langsmith.ipynb)
|
||||
- [How to return state before hitting recursion limit](return-when-recursion-limit-hits.ipynb)
|
||||
- [How to integrate LangGraph with AutoGen, CrewAI, and other frameworks](autogen-integration.ipynb)
|
||||
|
||||
### Prebuilt ReAct Agent
|
||||
|
||||
@@ -141,6 +142,7 @@ Learn how to set up your app for deployment to LangGraph Platform:
|
||||
- [How to customize Dockerfile](../cloud/deployment/custom_docker.md)
|
||||
- [How to test locally](../cloud/deployment/test_locally.md)
|
||||
- [How to rebuild graph at runtime](../cloud/deployment/graph_rebuild.md)
|
||||
- [How to use LangGraph Platform to deploy CrewAI, AutoGen, and other frameworks](autogen-langgraph-platform.ipynb)
|
||||
|
||||
### Deployment
|
||||
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
# How to connect a local agent to LangGraph Studio
|
||||
|
||||
This guide shows you how to connect your local agent to [LangGraph Studio](../concepts/langgraph_studio.md) for visualization, interaction, and debugging.
|
||||
|
||||
## Connection Options
|
||||
|
||||
There are two ways to connect your local agent to LangGraph Studio:
|
||||
|
||||
- [LangGraph Desktop](../concepts/langgraph_studio.md#desktop-app): Application, Mac only, requires Docker
|
||||
- [Development Server](../concepts/langgraph_studio.md#dev-server): Python package, all platforms, no Docker
|
||||
|
||||
In this guide we will cover how to use the development server as that is generally an easier and better experience.
|
||||
|
||||
## Setup your application
|
||||
|
||||
First, you will need to setup your application in the proper format.
|
||||
This means defining a `langgraph.json` file which contains paths to your agent(s).
|
||||
See [this guide](../concepts/application_structure.md) for information on how to do so.
|
||||
|
||||
## Install langgraph-cli
|
||||
|
||||
You will need to install [`langgraph-cli`](../cloud/reference/cli.md#langgraph-cli) (version `0.1.55` or higher).
|
||||
You will need to make sure to install the `inmem` extras.
|
||||
|
||||
```shell
|
||||
pip install "langgraph-cli[inmem]==0.1.55"
|
||||
```
|
||||
|
||||
## Run the development server
|
||||
|
||||
1. Navigate to your project directory (where `langgraph.json` is located)
|
||||
|
||||
2. Start the server:
|
||||
```bash
|
||||
langgraph dev
|
||||
```
|
||||
|
||||
This will look for the `langgraph.json` file in your current directory.
|
||||
In there, it will find the paths to the graph(s), and start those up.
|
||||
It will then automatically connect to the cloud-hosted studio.
|
||||
|
||||
## Use the studio
|
||||
|
||||
After connecting to the studio, a browser window should automatically pop up.
|
||||
This will use the cloud hosted studio UI to connect to your local development server.
|
||||
Your graph is still running locally, the UI is connecting to visualizing the agent and threads that are defined locally.
|
||||
|
||||
The graph will always use the most up-to-date code, so you will be able to change the underlying code and have it automatically reflected in the studio.
|
||||
This is useful for debugging workflows.
|
||||
You can run your graph in the UI until it messes up, go in and change your code, and then rerun from the node that failed.
|
||||
|
||||
# (Optional) Attach a debugger
|
||||
|
||||
For step-by-step debugging with breakpoints and variable inspection:
|
||||
|
||||
```bash
|
||||
# Install debugpy package
|
||||
pip install debugpy
|
||||
|
||||
# Start server with debugging enabled
|
||||
langgraph dev --debug-port 5678
|
||||
```
|
||||
|
||||
Then attach your preferred debugger:
|
||||
|
||||
=== "VS Code"
|
||||
Add this configuration to `launch.json`:
|
||||
```json
|
||||
{
|
||||
"name": "Attach to LangGraph",
|
||||
"type": "debugpy",
|
||||
"request": "attach",
|
||||
"connect": {
|
||||
"host": "0.0.0.0",
|
||||
"port": 5678
|
||||
}
|
||||
}
|
||||
```
|
||||
Specify the port number you chose in the previous step.
|
||||
|
||||
=== "PyCharm"
|
||||
1. Go to Run → Edit Configurations
|
||||
2. Click + and select "Python Debug Server"
|
||||
3. Set IDE host name: `localhost`
|
||||
4. Set port: `5678` (or the port number you chose in the previous step)
|
||||
5. Click "OK" and start debugging
|
||||
@@ -102,7 +102,7 @@
|
||||
"from langchain_community.document_loaders.recursive_url_loader import RecursiveUrlLoader\n",
|
||||
"\n",
|
||||
"# LCEL docs\n",
|
||||
"url = \"https://python.langchain.com/docs/concepts/#langchain-expression-language-lcel\"\n",
|
||||
"url = \"https://python.langchain.com/docs/concepts/lcel/\"\n",
|
||||
"loader = RecursiveUrlLoader(\n",
|
||||
" url=url, max_depth=20, extractor=lambda x: Soup(x, \"html.parser\").text\n",
|
||||
")\n",
|
||||
|
||||
@@ -35,12 +35,12 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"%%capture --no-stderr\n",
|
||||
"%pip install -U langgraph langchain langchain_openai langchain_experimental langsmith pandas"
|
||||
"%pip install -U langgraph langchain_community langchain_anthropic langchain_experimental"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"execution_count": null,
|
||||
"id": "30c2f3de-c730-4aec-85a6-af2c2f058803",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
@@ -54,7 +54,7 @@
|
||||
" os.environ[var] = getpass.getpass(f\"Please provide your {var}\")\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"_set_if_undefined(\"OPENAI_API_KEY\")\n",
|
||||
"_set_if_undefined(\"ANTHROPIC_API_KEY\")\n",
|
||||
"_set_if_undefined(\"TAVILY_API_KEY\")"
|
||||
]
|
||||
},
|
||||
@@ -83,7 +83,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"execution_count": 3,
|
||||
"id": "f04c6778-403b-4b49-9b93-678e910d5cec",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
@@ -91,45 +91,27 @@
|
||||
"from typing import Annotated\n",
|
||||
"\n",
|
||||
"from langchain_community.tools.tavily_search import TavilySearchResults\n",
|
||||
"from langchain_experimental.tools import PythonREPLTool\n",
|
||||
"from langchain_core.tools import tool\n",
|
||||
"from langchain_experimental.utilities import PythonREPL\n",
|
||||
"\n",
|
||||
"tavily_tool = TavilySearchResults(max_results=5)\n",
|
||||
"\n",
|
||||
"# This executes code locally, which can be unsafe\n",
|
||||
"python_repl_tool = PythonREPLTool()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "d58d1e85-22d4-4c22-9062-72a346a0d709",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Helper Utilities"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "b7c302b0-cd57-4913-986f-5dc7d6d77386",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Define a helper function that we will use to create the nodes in the graph - it takes care of converting the agent response to a human message. This is important because that is how we will add it the global state of the graph"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"id": "80862241-a1a7-4726-bce5-f867b233832e",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain_core.messages import HumanMessage\n",
|
||||
"repl = PythonREPL()\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def agent_node(state, agent, name):\n",
|
||||
" result = agent.invoke(state)\n",
|
||||
" return {\n",
|
||||
" \"messages\": [HumanMessage(content=result[\"messages\"][-1].content, name=name)]\n",
|
||||
" }"
|
||||
"@tool\n",
|
||||
"def python_repl_tool(\n",
|
||||
" code: Annotated[str, \"The python code to execute to generate your chart.\"],\n",
|
||||
"):\n",
|
||||
" \"\"\"Use this to execute python code and do math. If you want to see the output of a value,\n",
|
||||
" you should print it out with `print(...)`. This is visible to the user.\"\"\"\n",
|
||||
" try:\n",
|
||||
" result = repl.run(code)\n",
|
||||
" except BaseException as e:\n",
|
||||
" return f\"Failed to execute. Error: {repr(e)}\"\n",
|
||||
" result_str = f\"Successfully executed:\\n```python\\n{code}\\n```\\nStdout: {result}\"\n",
|
||||
" return result_str"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -139,57 +121,78 @@
|
||||
"source": [
|
||||
"### Create Agent Supervisor\n",
|
||||
"\n",
|
||||
"It will use function calling to choose the next worker node OR finish processing."
|
||||
"It will use LLM with structured output to choose the next worker node OR finish processing."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"execution_count": 4,
|
||||
"id": "f16c289b-10b0-47a8-a675-0bb54d299236",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langgraph.graph import MessagesState"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"id": "4a3baabf-18cb-415c-9473-d0546cf58b8b",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# The agent state is the input to each node in the graph\n",
|
||||
"class AgentState(MessagesState):\n",
|
||||
" # The 'next' field indicates where to route to next\n",
|
||||
" next: str"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 6,
|
||||
"id": "311f0a58-b425-4496-adac-dc4cd8ffb912",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder\n",
|
||||
"from langchain_openai import ChatOpenAI\n",
|
||||
"from pydantic import BaseModel\n",
|
||||
"from typing import Literal\n",
|
||||
"from typing_extensions import TypedDict\n",
|
||||
"\n",
|
||||
"from langchain_anthropic import ChatAnthropic\n",
|
||||
"from langgraph.graph import MessagesState\n",
|
||||
"\n",
|
||||
"members = [\"researcher\", \"coder\"]\n",
|
||||
"# Our team supervisor is an LLM node. It just picks the next agent to process\n",
|
||||
"# and decides when the work is completed\n",
|
||||
"options = members + [\"FINISH\"]\n",
|
||||
"\n",
|
||||
"members = [\"Researcher\", \"Coder\"]\n",
|
||||
"system_prompt = (\n",
|
||||
" \"You are a supervisor tasked with managing a conversation between the\"\n",
|
||||
" \" following workers: {members}. Given the following user request,\"\n",
|
||||
" f\" following workers: {members}. Given the following user request,\"\n",
|
||||
" \" respond with the worker to act next. Each worker will perform a\"\n",
|
||||
" \" task and respond with their results and status. When finished,\"\n",
|
||||
" \" respond with FINISH.\"\n",
|
||||
")\n",
|
||||
"# Our team supervisor is an LLM node. It just picks the next agent to process\n",
|
||||
"# and decides when the work is completed\n",
|
||||
"options = [\"FINISH\"] + members\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class routeResponse(BaseModel):\n",
|
||||
"class Router(TypedDict):\n",
|
||||
" \"\"\"Worker to route to next. If no workers needed, route to FINISH.\"\"\"\n",
|
||||
"\n",
|
||||
" next: Literal[*options]\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"prompt = ChatPromptTemplate.from_messages(\n",
|
||||
" [\n",
|
||||
" (\"system\", system_prompt),\n",
|
||||
" MessagesPlaceholder(variable_name=\"messages\"),\n",
|
||||
" (\n",
|
||||
" \"system\",\n",
|
||||
" \"Given the conversation above, who should act next?\"\n",
|
||||
" \" Or should we FINISH? Select one of: {options}\",\n",
|
||||
" ),\n",
|
||||
" ]\n",
|
||||
").partial(options=str(options), members=\", \".join(members))\n",
|
||||
"llm = ChatAnthropic(model=\"claude-3-5-sonnet-latest\")\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"llm = ChatOpenAI(model=\"gpt-4o\")\n",
|
||||
"def supervisor_node(state: AgentState) -> AgentState:\n",
|
||||
" messages = [\n",
|
||||
" {\"role\": \"system\", \"content\": system_prompt},\n",
|
||||
" ] + state[\"messages\"]\n",
|
||||
" response = llm.with_structured_output(Router).invoke(messages)\n",
|
||||
" next_ = response[\"next\"]\n",
|
||||
" if next_ == \"FINISH\":\n",
|
||||
" next_ = END\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def supervisor_agent(state):\n",
|
||||
" supervisor_chain = prompt | llm.with_structured_output(routeResponse)\n",
|
||||
" return supervisor_chain.invoke(state)"
|
||||
" return {\"next\": next_}"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -204,42 +207,57 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"execution_count": 7,
|
||||
"id": "6a430af7-8fce-4e66-ba9e-d940c1bc48e8",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"<langgraph.graph.state.StateGraph at 0x10abb3a50>"
|
||||
]
|
||||
},
|
||||
"execution_count": 7,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"import functools\n",
|
||||
"import operator\n",
|
||||
"from typing import Sequence\n",
|
||||
"from typing_extensions import TypedDict\n",
|
||||
"\n",
|
||||
"from langchain_core.messages import BaseMessage\n",
|
||||
"\n",
|
||||
"from langgraph.graph import END, StateGraph, START\n",
|
||||
"from langchain_core.messages import HumanMessage\n",
|
||||
"from langgraph.graph import StateGraph, START, END\n",
|
||||
"from langgraph.prebuilt import create_react_agent\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# The agent state is the input to each node in the graph\n",
|
||||
"class AgentState(TypedDict):\n",
|
||||
" # The annotation tells the graph that new messages will always\n",
|
||||
" # be added to the current states\n",
|
||||
" messages: Annotated[Sequence[BaseMessage], operator.add]\n",
|
||||
" # The 'next' field indicates where to route to next\n",
|
||||
" next: str\n",
|
||||
"research_agent = create_react_agent(\n",
|
||||
" llm, tools=[tavily_tool], state_modifier=\"You are a researcher. DO NOT do any math.\"\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"research_agent = create_react_agent(llm, tools=[tavily_tool])\n",
|
||||
"research_node = functools.partial(agent_node, agent=research_agent, name=\"Researcher\")\n",
|
||||
"def research_node(state: AgentState) -> AgentState:\n",
|
||||
" result = research_agent.invoke(state)\n",
|
||||
" return {\n",
|
||||
" \"messages\": [\n",
|
||||
" HumanMessage(content=result[\"messages\"][-1].content, name=\"researcher\")\n",
|
||||
" ]\n",
|
||||
" }\n",
|
||||
"\n",
|
||||
"# NOTE: THIS PERFORMS ARBITRARY CODE EXECUTION. PROCEED WITH CAUTION\n",
|
||||
"\n",
|
||||
"# NOTE: THIS PERFORMS ARBITRARY CODE EXECUTION, WHICH CAN BE UNSAFE WHEN NOT SANDBOXED\n",
|
||||
"code_agent = create_react_agent(llm, tools=[python_repl_tool])\n",
|
||||
"code_node = functools.partial(agent_node, agent=code_agent, name=\"Coder\")\n",
|
||||
"\n",
|
||||
"workflow = StateGraph(AgentState)\n",
|
||||
"workflow.add_node(\"Researcher\", research_node)\n",
|
||||
"workflow.add_node(\"Coder\", code_node)\n",
|
||||
"workflow.add_node(\"supervisor\", supervisor_agent)"
|
||||
"\n",
|
||||
"def code_node(state: AgentState) -> AgentState:\n",
|
||||
" result = code_agent.invoke(state)\n",
|
||||
" return {\n",
|
||||
" \"messages\": [HumanMessage(content=result[\"messages\"][-1].content, name=\"coder\")]\n",
|
||||
" }\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"builder = StateGraph(MessagesState)\n",
|
||||
"builder.add_edge(START, \"supervisor\")\n",
|
||||
"builder.add_node(\"supervisor\", supervisor_node)\n",
|
||||
"builder.add_node(\"researcher\", research_node)\n",
|
||||
"builder.add_node(\"coder\", code_node)"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -252,23 +270,53 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"execution_count": 8,
|
||||
"id": "14778e86-077b-4e6a-893c-400e59b0cdbf",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"for member in members:\n",
|
||||
" # We want our workers to ALWAYS \"report back\" to the supervisor when done\n",
|
||||
" workflow.add_edge(member, \"supervisor\")\n",
|
||||
" builder.add_edge(member, \"supervisor\")\n",
|
||||
"\n",
|
||||
"# The supervisor populates the \"next\" field in the graph state\n",
|
||||
"# which routes to a node or finishes\n",
|
||||
"conditional_map = {k: k for k in members}\n",
|
||||
"conditional_map[\"FINISH\"] = END\n",
|
||||
"workflow.add_conditional_edges(\"supervisor\", lambda x: x[\"next\"], conditional_map)\n",
|
||||
"builder.add_conditional_edges(\"supervisor\", lambda state: state[\"next\"])\n",
|
||||
"# Finally, add entrypoint\n",
|
||||
"workflow.add_edge(START, \"supervisor\")\n",
|
||||
"builder.add_edge(START, \"supervisor\")\n",
|
||||
"\n",
|
||||
"graph = workflow.compile()"
|
||||
"graph = builder.compile()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 9,
|
||||
"id": "fb2cf698-c42b-49ba-8ade-585d207a3daa",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from IPython.display import display, Image"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 10,
|
||||
"id": "f4992dcb-33c5-4fef-b4e3-09c303737342",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"image/png": "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 truncated
|
||||
"text/plain": [
|
||||
"<IPython.core.display.Image object>"
|
||||
]
|
||||
},
|
||||
"metadata": {},
|
||||
"output_type": "display_data"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"display(Image(graph.get_graph().draw_mermaid_png()))"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -283,7 +331,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 6,
|
||||
"execution_count": 11,
|
||||
"id": "56ba78e9-d9c1-457c-a073-d606d5d3e013",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
@@ -291,7 +339,7 @@
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"{'supervisor': {'next': 'Coder'}}\n",
|
||||
"((), {'supervisor': {'next': 'coder'}})\n",
|
||||
"----\n"
|
||||
]
|
||||
},
|
||||
@@ -306,9 +354,62 @@
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"{'Coder': {'messages': [HumanMessage(content='The code to print \"Hello, World!\" in the terminal has been executed successfully. Here is the output:\\n\\n```\\nHello, World!\\n```', additional_kwargs={}, response_metadata={}, name='Coder')]}}\n",
|
||||
"(('coder:a0c2a6de-4a2d-3573-4049-cba490183bc1',), {'agent': {'messages': [AIMessage(content=[{'text': \"I'll help you calculate the square root of 42 using Python.\", 'type': 'text'}, {'id': 'toolu_011Nsa2En2Qk1SsYBdG6zveY', 'input': {'code': 'import math\\nprint(math.sqrt(42))'}, 'name': 'python_repl_tool', 'type': 'tool_use'}], additional_kwargs={}, response_metadata={'id': 'msg_016CdBcK9JKm39tsuGH6skhN', 'model': 'claude-3-5-sonnet-20241022', 'stop_reason': 'tool_use', 'stop_sequence': None, 'usage': {'input_tokens': 435, 'output_tokens': 82}}, id='run-f9be84c7-1569-4f53-9063-b1244339755b-0', tool_calls=[{'name': 'python_repl_tool', 'args': {'code': 'import math\\nprint(math.sqrt(42))'}, 'id': 'toolu_011Nsa2En2Qk1SsYBdG6zveY', 'type': 'tool_call'}], usage_metadata={'input_tokens': 435, 'output_tokens': 82, 'total_tokens': 517, 'input_token_details': {}})]}})\n",
|
||||
"----\n",
|
||||
"{'supervisor': {'next': 'FINISH'}}\n",
|
||||
"(('coder:a0c2a6de-4a2d-3573-4049-cba490183bc1',), {'tools': {'messages': [ToolMessage(content='Successfully executed:\\n```python\\nimport math\\nprint(math.sqrt(42))\\n```\\nStdout: 6.48074069840786\\n', name='python_repl_tool', id='8b6bd229-5c63-43a4-9d63-e3b4a8468e21', tool_call_id='toolu_011Nsa2En2Qk1SsYBdG6zveY')]}})\n",
|
||||
"----\n",
|
||||
"(('coder:a0c2a6de-4a2d-3573-4049-cba490183bc1',), {'agent': {'messages': [AIMessage(content='The square root of 42 is approximately 6.4807 (rounded to 4 decimal places).', additional_kwargs={}, response_metadata={'id': 'msg_01QYQtz84F1Mgqyp2ecw4TEu', 'model': 'claude-3-5-sonnet-20241022', 'stop_reason': 'end_turn', 'stop_sequence': None, 'usage': {'input_tokens': 561, 'output_tokens': 28}}, id='run-b9dfff5d-f1c4-44d6-98d7-80f0e8548bcd-0', usage_metadata={'input_tokens': 561, 'output_tokens': 28, 'total_tokens': 589, 'input_token_details': {}})]}})\n",
|
||||
"----\n",
|
||||
"((), {'coder': {'messages': [HumanMessage(content='The square root of 42 is approximately 6.4807 (rounded to 4 decimal places).', additional_kwargs={}, response_metadata={}, name='coder')]}})\n",
|
||||
"----\n",
|
||||
"((), {'supervisor': {'next': '__end__'}})\n",
|
||||
"----\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"for s in graph.stream(\n",
|
||||
" {\"messages\": [(\"user\", \"What's the square root of 42?\")]}, subgraphs=True\n",
|
||||
"):\n",
|
||||
" print(s)\n",
|
||||
" print(\"----\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 12,
|
||||
"id": "45a92dfd-0e11-47f5-aad4-b68d24990e34",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"((), {'supervisor': {'next': 'researcher'}})\n",
|
||||
"----\n",
|
||||
"(('researcher:7daea379-a5b6-6d3d-ef85-fffc96d7472e',), {'agent': {'messages': [AIMessage(content=[{'text': \"I'll help you search for the GDP data of New York and California using the search tool. Then I'll note the values, but as instructed, I won't perform the mathematical calculation myself.\", 'type': 'text'}, {'id': 'toolu_01S9hPD5nFsW1A2nE4fwCvRc', 'input': {'query': 'latest GDP of New York state 2023'}, 'name': 'tavily_search_results_json', 'type': 'tool_use'}], additional_kwargs={}, response_metadata={'id': 'msg_01RetKetMGpP2Q51w4R8N81e', 'model': 'claude-3-5-sonnet-20241022', 'stop_reason': 'tool_use', 'stop_sequence': None, 'usage': {'input_tokens': 442, 'output_tokens': 107}}, id='run-6e738192-18ae-4c1a-bbc0-f8b8509fe656-0', tool_calls=[{'name': 'tavily_search_results_json', 'args': {'query': 'latest GDP of New York state 2023'}, 'id': 'toolu_01S9hPD5nFsW1A2nE4fwCvRc', 'type': 'tool_call'}], usage_metadata={'input_tokens': 442, 'output_tokens': 107, 'total_tokens': 549, 'input_token_details': {}})]}})\n",
|
||||
"----\n",
|
||||
"(('researcher:7daea379-a5b6-6d3d-ef85-fffc96d7472e',), {'tools': {'messages': [ToolMessage(content='[{\"url\": \"https://usafacts.org/metrics/gross-domestic-product-gdp-by-state-new-york/\", \"content\": \"Gross domestic product (GDP) state — New York (dollars) Adjustment. None. Adjustment. Frequency. Yearly. Frequency. In 2022 (most recent), Gross domestic product (GDP) was $2,053,179,700,000 in the United States for New York (state). ... August 25, 2023. Suggested citation: Explore in... Less detail\"}, {\"url\": \"https://www.osc.ny.gov/reports/finance/2023-fcr/economic-and-demographic-trends\", \"content\": \"These include, but are not limited to:\\\\nBecause Google Translate™ is intellectual property owned by Google Inc., you must use Google Translate™ in accord with the Google license agreement, which includes potential liability for misuse: Google Terms of Service.\\\\nOffice of the NEW YORK\\\\nSTATE COMPTROLLER\\\\nNYS Comptroller Thomas P. DiNapoli\\\\nMain navigation\\\\nGET to KnowNew York State ComptrollerThomas P. DiNapoli\\\\nRead BIO\\\\nGET to KnowNew York State ComptrollerThomas P. DiNapoli\\\\nMenu\\\\nEconomic and Demographic Trends\\\\n2023 Financial Condition Report For Fiscal Year Ended March 31, 2023\\\\nEmployment Still Below Pre- Pandemic Levels in 2022\\\\nNew York Ranked 45th Nationwide for Personal Income Growth in 2022\\\\nNYS GDP Nearly $1.6 Trillion in 2022\\\\nA state’s Gross Domestic Product (GDP) is the value of production originating from all industries in the state, as defined by the U.S. Bureau of Economic Analysis.\\\\n The State of New York, its officers, employees, and/or agents are not liable to you, or to third parties, for damages or losses of any kind arising out of, or in connection with, the use or performance of such information. New York’s Population Continued to Decline in 2022\\\\nBook traversal links for Economic and Demographic Trends\\\\nTell us more about you to receive content related to your area or interests.\\\\n The Office of the State Comptroller does not warrant, promise, assure or guarantee the accuracy of the translations provided.\"}, {\"url\": \"https://www.statista.com/statistics/188087/gdp-of-the-us-federal-state-of-new-york-since-1997/\", \"content\": \"Industry Overview\\\\nDigital & Trend reports\\\\nOverview and forecasts on trending topics\\\\nIndustry & Market reports\\\\nIndustry and market insights and forecasts\\\\nCompanies & Products reports\\\\nKey figures and rankings about companies and products\\\\nConsumer & Brand reports\\\\nConsumer and brand insights and preferences in various industries\\\\nPolitics & Society reports\\\\nDetailed information about political and social topics\\\\nCountry & Region reports\\\\nAll key figures about countries and regions\\\\nMarket forecast and expert KPIs for 1000+ markets in 190+ countries & territories\\\\nInsights on consumer attitudes and behavior worldwide\\\\nBusiness information on 100m+ public and private companies\\\\nExplore Company Insights\\\\nDetailed information for 39,000+ online stores and marketplaces\\\\nDirectly accessible data for 170 industries from 150+ countries\\\\nand over 1\\xa0Mio. facts.\\\\n Transforming data into design:\\\\nStatista Content & Design\\\\nStrategy and business building for the data-driven economy:\\\\nU.S. real GDP of New York 2000-2022\\\\nReal gross domestic product of New York in the United States\\\\nfrom 2000 to 2022\\\\n(in billion U.S. dollars)\\\\nAdditional Information\\\\nShow sources information\\\\nShow publisher information\\\\nUse Ask Statista Research Service\\\\nMarch 2023\\\\nUnited States\\\\n2000 to 2022\\\\nData presented here is in 2012 chained U.S. dollars.\\\\n Statistics on\\\\n\\\\\"\\\\nNew York\\\\n\\\\\"\\\\nOther statistics that may interest you New York\\\\nPopulation\\\\nEconomy\\\\nEmployment & Earnings\\\\nState & Local Government\\\\nNew York City\\\\nFurther related statistics\\\\nFurther Content: You might find this interesting as well\\\\nStatistics\\\\nTopics Other statistics on the topic\\\\nEconomy\\\\nU.S. real gross domestic product 2022, by state\\\\nPolitics & Government\\\\nU.S. state and local government outstanding debt 2021, by state\\\\nDemographics\\\\nResident population in New York 1960-2022\\\\nEconomy\\\\nU.S. New York metro area GDP 2001-2022\\\\nYou only have access to basic statistics.\\\\n Customized Research & Analysis projects:\\\\nGet quick analyses with our professional research service\\\\nThe best of the best: the portal for top lists & rankings:\\\\n\"}, {\"url\": \"https://www.statista.com/statistics/306777/new-york-gdp-growth/\", \"content\": \"Industry Overview\\\\nDigital & Trend reports\\\\nOverview and forecasts on trending topics\\\\nIndustry & Market reports\\\\nIndustry and market insights and forecasts\\\\nCompanies & Products reports\\\\nKey figures and rankings about companies and products\\\\nConsumer & Brand reports\\\\nConsumer and brand insights anLine truncated
|
||||
"----\n",
|
||||
"(('researcher:7daea379-a5b6-6d3d-ef85-fffc96d7472e',), {'agent': {'messages': [AIMessage(content=[{'id': 'toolu_015fdnpWUiuEshsEwn2nBJ1g', 'input': {'query': 'latest GDP of California state 2023'}, 'name': 'tavily_search_results_json', 'type': 'tool_use'}], additional_kwargs={}, response_metadata={'id': 'msg_01Cksgb2aaqcD2bPtam5HDPF', 'model': 'claude-3-5-sonnet-20241022', 'stop_reason': 'tool_use', 'stop_sequence': None, 'usage': {'input_tokens': 2534, 'output_tokens': 66}}, id='run-11d924ba-a494-49d6-b649-ac961e31c79c-0', tool_calls=[{'name': 'tavily_search_results_json', 'args': {'query': 'latest GDP of California state 2023'}, 'id': 'toolu_015fdnpWUiuEshsEwn2nBJ1g', 'type': 'tool_call'}], usage_metadata={'input_tokens': 2534, 'output_tokens': 66, 'total_tokens': 2600, 'input_token_details': {}})]}})\n",
|
||||
"----\n",
|
||||
"(('researcher:7daea379-a5b6-6d3d-ef85-fffc96d7472e',), {'tools': {'messages': [ToolMessage(content='[{\"url\": \"https://www.gov.ca.gov/2024/04/16/california-remains-the-worlds-5th-largest-economy/\", \"content\": \"California remains the 5th largest economy in the world since 2017. California is the 5th largest economy in the world for the seventh consecutive year, with a nominal GDP of nearly $3.9 trillion in 2023 and a growth rate of 6.1% since the year prior, according to the U.S. Bureau of Economic Analysis (BEA). On a per capita basis, California is\"}, {\"url\": \"https://usafacts.org/metrics/gross-domestic-product-gdp-by-state-california/\", \"content\": \"USAFacts -- In 2022 (most recent), Gross domestic product (GDP) was 3598102700000.0 in the United States for California (state). This increased by 224,862,000,000 or 6.67% from 2021. Highest: 3,598,102,700,000 in 2022.\"}, {\"url\": \"https://www.statista.com/statistics/187834/gdp-of-the-us-federal-state-of-california-since-1997/\", \"content\": \"Industry Overview\\\\nDigital & Trend reports\\\\nOverview and forecasts on trending topics\\\\nIndustry & Market reports\\\\nIndustry and market insights and forecasts\\\\nCompanies & Products reports\\\\nKey figures and rankings about companies and products\\\\nConsumer & Brand reports\\\\nConsumer and brand insights and preferences in various industries\\\\nPolitics & Society reports\\\\nDetailed information about political and social topics\\\\nCountry & Region reports\\\\nAll key figures about countries and regions\\\\nMarket forecast and expert KPIs for 1000+ markets in 190+ countries & territories\\\\nInsights on consumer attitudes and behavior worldwide\\\\nBusiness information on 100m+ public and private companies\\\\nExplore Company Insights\\\\nDetailed information for 39,000+ online stores and marketplaces\\\\nDirectly accessible data for 170 industries from 150+ countries\\\\nand over 1\\xa0Mio. facts.\\\\n Statistics on\\\\n\\\\\"\\\\nCalifornia\\\\n\\\\\"\\\\nOther statistics that may interest you California\\\\nPopulation\\\\nEconomy\\\\nEmployment & Earnings\\\\nState & Local Government\\\\nMetro Areas\\\\nFurther related statistics\\\\nFurther Content: You might find this interesting as well\\\\nStatistics\\\\nTopics Other statistics on the topicCalifornia\\\\nEconomy\\\\nU.S. leading companies headquartered in California 2023, by number of employees\\\\nEconomy\\\\nU.S. average annual wages in California 2018-2026\\\\nEconomy\\\\nU.S. California fastest growing private companies 2023, by three year growth rate\\\\nResidential Real Estate\\\\nHourly wages needed to afford a two-bedroom apartment in California 2021-23, by metro\\\\nYou only have access to basic statistics.\\\\n Additional Information\\\\nShow sources information\\\\nShow publisher information\\\\nUse Ask Statista Research Service\\\\nMarch 2023\\\\nUnited States\\\\n2000 to 2022\\\\nData presented here is in 2012 chained U.S. dollars.\\\\n Transforming data into design:\\\\nStatista Content & Design\\\\nStrategy and business building for the data-driven economy:\\\\nU.S. real GDP of California 2000-2022\\\\nReal gross domestic product of California in the United States from 2000 to 2022\\\\n(in billion U.S. dollars)\\\\n\"}, {\"url\": \"https://www.bea.gov/news/2024/gross-domestic-product-state-and-personal-income-state-4th-quarter-2023-and-preliminary\", \"content\": \"Real gross domestic product (GDP) increased in all 50 states and the District of Columbia in the fourth quarter of 2023, with the percent change ranging from 6.7 percent in Nevada to 0.2 percent in Nebraska (table 1), according to statistics released today by the U.S. Bureau of Economic Analysis (BEA). Current-dollar GDP increased in 49 states and the District of Columbia.\"}, {\"url\": \"https://www.bea.gov/news/2023/gross-domestic-product-state-and-personal-income-state-1st-quarter-2023\", \"content\": \"Real gross domestic product (GDP) increased in all 50 states and the District of Columbia in the first quarter of 2023, with the percent change ranging from 12.4 percent in North Dakota to 0.1 percent in Rhode Island and Alabama (table 1), according to statistics released today by the U.S. Bureau of Economic Analysis (BEA). Current-dollar GDP increased in 47 states and the District of Columbia\"}]', name='tavily_search_results_json', id='77de7955-ba39-4db3-9460-1a2bd7f602fb', tool_call_id='toolu_015fdnpWUiuEshsEwn2nBJ1g', artifact={'query': 'latest GDP of California state 2023', 'follow_up_questions': None, 'answer': None, 'images': [], 'results': [{'title': \"California Remains the World's 5th Largest Economy\", 'url': 'https://www.gov.ca.gov/2024/04/16/california-remains-the-worlds-5th-largest-economy/', 'content': 'California remains the 5th largest economy in the world since 2017. California is the 5th largest economy in the world for the seventh consecutive year, with a nominal GDP of nearly $3.9 trillion in 2023 and a growth rate of 6.1% sLine truncated
|
||||
"----\n",
|
||||
"(('researcher:7daea379-a5b6-6d3d-ef85-fffc96d7472e',), {'agent': {'messages': [AIMessage(content=\"Based on the search results, I can provide you with the latest GDP figures for both states:\\n\\nNew York:\\n- GDP: $2.053 trillion (2022 figures)\\n\\nCalifornia:\\n- GDP: $3.9 trillion (2023 figures)\\n\\nAs instructed, I won't calculate the average, but I've provided you with the most recent GDP figures for both states. Note that the figures are from different years (2022 for NY and 2023 for CA), which should be considered when calculating the average.\", additional_kwargs={}, response_metadata={'id': 'msg_013yuy2PoBUNSGNDCYXvUL27', 'model': 'claude-3-5-sonnet-20241022', 'stop_reason': 'end_turn', 'stop_sequence': None, 'usage': {'input_tokens': 3748, 'output_tokens': 120}}, id='run-d775159b-c114-4db5-b425-416f0f2f957c-0', usage_metadata={'input_tokens': 3748, 'output_tokens': 120, 'total_tokens': 3868, 'input_token_details': {}})]}})\n",
|
||||
"----\n",
|
||||
"((), {'researcher': {'messages': [HumanMessage(content=\"Based on the search results, I can provide you with the latest GDP figures for both states:\\n\\nNew York:\\n- GDP: $2.053 trillion (2022 figures)\\n\\nCalifornia:\\n- GDP: $3.9 trillion (2023 figures)\\n\\nAs instructed, I won't calculate the average, but I've provided you with the most recent GDP figures for both states. Note that the figures are from different years (2022 for NY and 2023 for CA), which should be considered when calculating the average.\", additional_kwargs={}, response_metadata={}, name='researcher')]}})\n",
|
||||
"----\n",
|
||||
"((), {'supervisor': {'next': 'coder'}})\n",
|
||||
"----\n",
|
||||
"(('coder:2c47a596-d75b-143e-9b4a-a99f78779aec',), {'agent': {'messages': [AIMessage(content=[{'text': \"I'll help calculate the average GDP between New York ($2.053 trillion) and California ($3.9 trillion).\", 'type': 'text'}, {'id': 'toolu_019yGU4aBc9H73jfRWr1iKtf', 'input': {'code': 'ny_gdp = 2.053\\nca_gdp = 3.9\\n\\naverage_gdp = (ny_gdp + ca_gdp) / 2\\n\\nprint(f\"New York GDP: ${ny_gdp} trillion\")\\nprint(f\"California GDP: ${ca_gdp} trillion\")\\nprint(f\"Average GDP: ${average_gdp:.3f} trillion\")'}, 'name': 'python_repl_tool', 'type': 'tool_use'}], additional_kwargs={}, response_metadata={'id': 'msg_01PGbexsWGaf4LQbKDH8toJs', 'model': 'claude-3-5-sonnet-20241022', 'stop_reason': 'tool_use', 'stop_sequence': None, 'usage': {'input_tokens': 558, 'output_tokens': 175}}, id='run-355dc875-adcc-436c-b0f3-675dc1c099bd-0', tool_calls=[{'name': 'python_repl_tool', 'args': {'code': 'ny_gdp = 2.053\\nca_gdp = 3.9\\n\\naverage_gdp = (ny_gdp + ca_gdp) / 2\\n\\nprint(f\"New York GDP: ${ny_gdp} trillion\")\\nprint(f\"California GDP: ${ca_gdp} trillion\")\\nprint(f\"Average GDP: ${average_gdp:.3f} trillion\")'}, 'id': 'toolu_019yGU4aBc9H73jfRWr1iKtf', 'type': 'tool_call'}], usage_metadata={'input_tokens': 558, 'output_tokens': 175, 'total_tokens': 733, 'input_token_details': {}})]}})\n",
|
||||
"----\n",
|
||||
"(('coder:2c47a596-d75b-143e-9b4a-a99f78779aec',), {'tools': {'messages': [ToolMessage(content='Successfully executed:\\n```python\\nny_gdp = 2.053\\nca_gdp = 3.9\\n\\naverage_gdp = (ny_gdp + ca_gdp) / 2\\n\\nprint(f\"New York GDP: ${ny_gdp} trillion\")\\nprint(f\"California GDP: ${ca_gdp} trillion\")\\nprint(f\"Average GDP: ${average_gdp:.3f} trillion\")\\n```\\nStdout: New York GDP: $2.053 trillion\\nCalifornia GDP: $3.9 trillion\\nAverage GDP: $2.976 trillion\\n', name='python_repl_tool', id='39106042-eb3e-485c-8d62-bb2f572fbf8b', tool_call_id='toolu_019yGU4aBc9H73jfRWr1iKtf')]}})\n",
|
||||
"----\n",
|
||||
"(('coder:2c47a596-d75b-143e-9b4a-a99f78779aec',), {'agent': {'messages': [AIMessage(content=\"Based on the calculations:\\n- New York's GDP: $2.053 trillion (2022)\\n- California's GDP: $3.9 trillion (2023)\\n- The average GDP between the two states is $2.976 trillion\\n\\nNote: As mentioned earlier, these GDP figures are from different years (2022 for NY and 2023 for CA), which should be taken into consideration when interpreting the average.\", additional_kwargs={}, response_metadata={'id': 'msg_016GGgrPRH3psSoWUw7TzTQu', 'model': 'claude-3-5-sonnet-20241022', 'stop_reason': 'end_turn', 'stop_sequence': None, 'usage': {'input_tokens': 877, 'output_tokens': 98}}, id='run-55436cea-0fab-4fa3-a691-3a80a0afb431-0', usage_metadata={'input_tokens': 877, 'output_tokens': 98, 'total_tokens': 975, 'input_token_details': {}})]}})\n",
|
||||
"----\n",
|
||||
"((), {'coder': {'messages': [HumanMessage(content=\"Based on the calculations:\\n- New York's GDP: $2.053 trillion (2022)\\n- California's GDP: $3.9 trillion (2023)\\n- The average GDP between the two states is $2.976 trillion\\n\\nNote: As mentioned earlier, these GDP figures are from different years (2022 for NY and 2023 for CA), which should be taken into consideration when interpreting the average.\", additional_kwargs={}, response_metadata={}, name='coder')]}})\n",
|
||||
"----\n",
|
||||
"((), {'supervisor': {'next': '__end__'}})\n",
|
||||
"----\n"
|
||||
]
|
||||
}
|
||||
@@ -317,46 +418,16 @@
|
||||
"for s in graph.stream(\n",
|
||||
" {\n",
|
||||
" \"messages\": [\n",
|
||||
" HumanMessage(content=\"Code hello world and print it to the terminal\")\n",
|
||||
" (\n",
|
||||
" \"user\",\n",
|
||||
" \"Find the latest GDP of New York and California, then calculate the average\",\n",
|
||||
" )\n",
|
||||
" ]\n",
|
||||
" }\n",
|
||||
" },\n",
|
||||
" subgraphs=True,\n",
|
||||
"):\n",
|
||||
" if \"__end__\" not in s:\n",
|
||||
" print(s)\n",
|
||||
" print(\"----\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 7,
|
||||
"id": "45a92dfd-0e11-47f5-aad4-b68d24990e34",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"{'supervisor': {'next': 'Researcher'}}\n",
|
||||
"----\n",
|
||||
"{'Researcher': {'messages': [HumanMessage(content='### Research Report on Pikas\\n\\n#### Introduction\\nPikas are small, herbivorous mammals belonging to the family Ochotonidae, closely related to rabbits and hares. These animals are known for their distinctive high-pitched calls and are often found in cold, mountainous regions across Asia, North America, and parts of Europe.\\n\\n#### Habitat and Behavior\\nPikas primarily inhabit talus slopes and alpine meadows, often at elevations ranging from 2,500 to over 13,000 feet. These environments provide the necessary rock crevices and vegetation required for their survival. Pikas are diurnal and exhibit two main foraging behaviors: direct consumption of plants and the collection of vegetation into \"haypiles\" for winter storage. Unlike many small mammals, pikas do not hibernate and remain active throughout the winter, relying on these haypiles for sustenance.\\n\\n#### Diet and Feeding Habits\\nPikas are generalist herbivores, feeding on a variety of grasses, forbs, and small shrubs. They have a highly developed behavior known as \"haying,\" where they collect and store plant material during the summer months to ensure a food supply during the harsh winter. This behavior is crucial for their survival, as the stored hay provides the necessary nutrients when fresh vegetation is scarce.\\n\\n#### Reproduction and Lifecycle\\nPikas have a relatively short lifespan, averaging around three years. They typically breed once or twice a year, with a gestation period of roughly 30 days. Females usually give birth to litters of two to six young. The young are weaned and become independent within a month, reaching sexual maturity by the following spring.\\n\\n#### Conservation Status\\nThe conservation status of pikas varies by region and species. The American pika (Ochotona princeps), found in the mountains of western North America, is particularly vulnerable to climate change. Rising temperatures and reduced snowpack threaten their habitat, forcing pikas to move to higher elevations or face local extirpation. Despite these challenges, the American pika is not currently listed under the US Endangered Species Act, although several studies indicate localized population declines.\\n\\n#### Conclusion\\nPikas are fascinating creatures that play a vital role in their alpine ecosystems. Their unique behaviors, such as haying, and their sensitivity to climate change make them important indicators of environmental health. Continued research and conservation efforts are essential to ensure the survival of these small but significant mammals in the face of global climatic shifts.\\n\\n#### References\\n1. Wikipedia - Pika: [Link](https://en.wikipedia.org/wiki/Pika)\\n2. Wikipedia - American Pika: [Link](https://en.wikipedia.org/wiki/American_pika)\\n3. Animal Spot - American Pika: [Link](https://www.animalspot.net/american-pika.html)\\n4. Animalia - American Pika: [Link](https://animalia.bio/index.php/american-pika)\\n5. National Park Service - Pikas Resource Brief: [Link](https://www.nps.gov/articles/pikas-brief.htm)\\n6. Alaska Department of Fish and Game - Pikas: [Link](https://www.adfg.alaska.gov/static/education/wns/pikas.pdf)\\n7. NatureMapping Foundation - American Pika: [Link](http://naturemappingfoundation.org/natmap/facts/american_pika_712.html)\\n8. USDA Forest Service - Conservation Status of Pikas: [Link](https://www.fs.usda.gov/psw/publications/millar/psw_2022_millar002.pdf)', additional_kwargs={}, response_metadata={}, name='Researcher')]}}\n",
|
||||
"----\n",
|
||||
"{'supervisor': {'next': 'Coder'}}\n",
|
||||
"----\n",
|
||||
"{'Coder': {'messages': [HumanMessage(content='### Research Report on Pikas\\n\\n#### Introduction\\nPikas are small, herbivorous mammals belonging to the family Ochotonidae, closely related to rabbits and hares. These animals are known for their distinctive high-pitched calls and are often found in cold, mountainous regions across Asia, North America, and parts of Europe.\\n\\n#### Habitat and Behavior\\nPikas primarily inhabit talus slopes and alpine meadows, often at elevations ranging from 2,500 to over 13,000 feet. These environments provide the necessary rock crevices and vegetation required for their survival. Pikas are diurnal and exhibit two main foraging behaviors: direct consumption of plants and the collection of vegetation into \"haypiles\" for winter storage. Unlike many small mammals, pikas do not hibernate and remain active throughout the winter, relying on these haypiles for sustenance.\\n\\n#### Diet and Feeding Habits\\nPikas are generalist herbivores, feeding on a variety of grasses, forbs, and small shrubs. They have a highly developed behavior known as \"haying,\" where they collect and store plant material during the summer months to ensure a food supply during the harsh winter. This behavior is crucial for their survival, as the stored hay provides the necessary nutrients when fresh vegetation is scarce.\\n\\n#### Reproduction and Lifecycle\\nPikas have a relatively short lifespan, averaging around three years. They typically breed once or twice a year, with a gestation period of roughly 30 days. Females usually give birth to litters of two to six young. The young are weaned and become independent within a month, reaching sexual maturity by the following spring.\\n\\n#### Conservation Status\\nThe conservation status of pikas varies by region and species. The American pika (Ochotona princeps), found in the mountains of western North America, is particularly vulnerable to climate change. Rising temperatures and reduced snowpack threaten their habitat, forcing pikas to move to higher elevations or face local extirpation. Despite these challenges, the American pika is not currently listed under the US Endangered Species Act, although several studies indicate localized population declines.\\n\\n#### Conclusion\\nPikas are fascinating creatures that play a vital role in their alpine ecosystems. Their unique behaviors, such as haying, and their sensitivity to climate change make them important indicators of environmental health. Continued research and conservation efforts are essential to ensure the survival of these small but significant mammals in the face of global climatic shifts.\\n\\n#### References\\n1. Wikipedia - Pika: [Link](https://en.wikipedia.org/wiki/Pika)\\n2. Wikipedia - American Pika: [Link](https://en.wikipedia.org/wiki/American_pika)\\n3. Animal Spot - American Pika: [Link](https://www.animalspot.net/american-pika.html)\\n4. Animalia - American Pika: [Link](https://animalia.bio/index.php/american-pika)\\n5. National Park Service - Pikas Resource Brief: [Link](https://www.nps.gov/articles/pikas-brief.htm)\\n6. Alaska Department of Fish and Game - Pikas: [Link](https://www.adfg.alaska.gov/static/education/wns/pikas.pdf)\\n7. NatureMapping Foundation - American Pika: [Link](http://naturemappingfoundation.org/natmap/facts/american_pika_712.html)\\n8. USDA Forest Service - Conservation Status of Pikas: [Link](https://www.fs.usda.gov/psw/publications/millar/psw_2022_millar002.pdf)', additional_kwargs={}, response_metadata={}, name='Coder')]}}\n",
|
||||
"----\n",
|
||||
"{'supervisor': {'next': 'FINISH'}}\n",
|
||||
"----\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"for s in graph.stream(\n",
|
||||
" {\"messages\": [HumanMessage(content=\"Write a brief research report on pikas.\")]},\n",
|
||||
" {\"recursion_limit\": 100},\n",
|
||||
"):\n",
|
||||
" if \"__end__\" not in s:\n",
|
||||
" print(s)\n",
|
||||
" print(\"----\")"
|
||||
" print(s)\n",
|
||||
" print(\"----\")"
|
||||
]
|
||||
}
|
||||
],
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
"cells": [
|
||||
{
|
||||
"attachments": {
|
||||
"50a6ed47-ace3-428e-8dcf-a13ec56c11d6.png": {
|
||||
"image/png": "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 truncated
|
||||
"d98ed25c-51cb-441f-a6f4-016921d59fc3.png": {
|
||||
"image/png": "iVBORw0KGgoAAAANSUhEUgAABaIAAAP8CAIAAAA7q14UAAAAAXNSR0IArs4c6QAAAERlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAAFoqADAAQAAAABAAAD/AAAAADsXbjNAABAAElEQVR4AezdecAkRX03cFfwDhgVFRBY1JgoEI1HfPbxgnjiAV7AridiNB4R3UVNBPGViEcC6q6Jxmg8Iom6C14QQRAUJcrugxfegKKugopX5BDEa9+vlGmGeY6dZ545uns+zx9rT091ddWn5nmkf/OrqmVbtmy5nh8CBAgQIECAAAECBAgQIECAQPMFrt/8LugBAQIECBAgQIAAAQIECBAgQOD3AsIcPgcECBAgQIAAAQIECBAgQIBASwSEOVoykLpBgAABAgQIECBAgAABAgQICHP4DBAgQIAAAQIECBAgQIAAAQItERDmaMlA6gYBAgQIECBAgAABAgQIECAgzOEzQIAAAQIECBAgQIAAAQIECLREQJijJQOpGwQIECBAgAABAgQIECBAgIAwh88AAQIECBAgQIAAAQIECBAg0BIBYY6WDKRuECBAgAABAgQIECBAgAABAsIcPgMECBAgQIAAAQIECBAgQIBASwSEOVoykLpBgAABAgQIECBAgAABAgQICHP4DBAgQIAAAQIECBAgQIAAAQItERDmaMlA6gYBAgQIECBAgAABAgQIECAgzOEzQIAAAQIECBAgQIAAAQIECLREQJijJQOpGwQIECBAgAABAgQIECBAgIAwh88AAQIECBAgQIAAAQIECBAg0BIBYY6WDKRuECBAgAABAgQIECBAgAABAsIcPgMECBAgQIAAAQIECBAgQIBASwSEOVoykLpBgAABAgQIECBAgAABAgQICHP4DBAgQIAAAQIECBAgQIAAAQItERDmaMlA6gYBAgQIECBAgAABAgQIECAgzOEzQIAAAQIECBAgQIAAAQIECLREQJijJQOpGwQIECBAgAABAgQIECBAgIAwh88AAQIECBAgQIAAAQIECBAg0BIBYY6WDKRuECBAgAABAgQIECBAgAABAsIcPgMECBAgQIAAAQIECBAgQIBASwSEOVoykLpBgAABAgQIECBAgAABAgQICHP4DBAgQIAAAQIECBAgQIAAAQItERDmaMlA6gYBAgQIECBAgAABAgQIECAgzOEzQIAAAQIECBAgQIAAAQIECLREQJijJQOpGwQIECBAgAABAgQIECBAgIAwh88AAQIECBAgQIAAAQIECBAg0BIBYY6WDKRuECBAgAABAgQIECBAgAABAsIcPgMECBAgQIAAAQIECBAgQIBASwSEOVoykLpBgAABAgQIECBAgAABAgQICHP4DBAgQIAAAQIECBAgQIAAAQItERDmaMlA6gYBAgQIECBAgAABAgQIECAgzOEzQIAAAQIECBAgQIAAAQIECLREQJijJQOpGwQIECBAgAABAgQIECBAgIAwh88AAQIECBAgQIAAAQIECBAg0BIBYY6WDKRuECBAgAABAgQIECBAgAABAsIcPgMECBAgQIAAAQIECBAgQIBASwSEOVoykLpBgAABAgQIECBAgAABAgQICHP4DBAgQIAAAQIECBAgQIAAAQItERDmaMlA6gYBAgQIECBAgAABAgQIECAgzOEzQIAAAQIECBAgQIAAAQIECLREQJijJQOpGwQIECBAgAABAgQIECBAgIAwh88AAQIECBAgQIAAAQIECBAg0BIBYY6WDKRuECBAgAABAgQIECBAgAABAsIcPgMECBAgQIAAAQIECBAgQIBASwSEOVoykLpBgAABAgQIECBAgAABAgQICHP4DBAgQIAAAQIECBAgQIAAAQItERDmaMlA6gYBAgQIECBAgAABAgQIECAgzOEzQIAAAQIECBAgQIAAAQIECLREQJijJQOpGwQIECBAgAABAgQIECBAgMC2CAgQIECAwCQIbNq0qXRz48aNLevvzMxM3z2amprq+1oXEhiXwPT0dG69YsWKcTXAfQkQIECgzgLLtmzZUuf2aRsBAgQIEOhPIHGNdevWlWsT2ijPRXmqLwf91Tn7quYGTZYSHJnt4EyjBcYS7Vrsb2L1u1Y+utXL1atXBz+1iXo0+kOo8QQIEBiggDDHADFVRYAAAQK1EOgMcJRHIM8/tRgYjSAwUIH8ppdgRxXQzO/7mjVrBnoTlREgQIBA8wSEOZo3ZlpMgAABAnMKlOhGHnvyvW6edoQ25lRykkArBdauXZt+lXiHYEcrh1inCBAg0LuAtTl6t1KSAAECBOorkIecPOHk8SY/Ahz1HSctIzAcgc4kjhLsMI1lONJqJUCAQAMEZHM0YJA0kQABAgQWFli1alUKrF+/fuFi3iVAYBIEStAzPd2wYYOg5ySMuD4SIECgS8CGsl0gXhIgQIBAkwQyUWX58uVZQFGMo0nDpq0EhimQzI7Nmzcnm2PlypX5EzHMW6mbAAECBOooIJujjqOiTQQIECDQi0C+s82eC2ap9GKlDIEJFEieVxbrkdMxgUOvywQITLiAbI4J/wDoPgECBJoqkC9pE+NIEoek9KYOoXYTGLJAYqC5Q7UPy5DvpnoCBAgQqIuAMEddRkI7CBAgQGBRAklHL88wi7pKYQIEJkcgMdCkciShw9SVyRl0PSVAgEAETFrxMSBAgACB5gmUzSM791ZoXh+0mACBkQhYongkzG5CgACBGgkIc9RoMDSFAAECBHoREOPoRUkZAgQqgSxUnOQvgdEKxAEBAgTaLSDM0e7x1TsCBAi0TSDJ55mukm0U2tYx/SFAYGgCZYtZfzeGBqxiAgQI1EvA2hz1Gg+tIUCAAIGFBTLN3pIcCxN5lwCBLoGSx2GFji4WLwkQINBWAdkcbR1Z/SJAgEA7BZJ87ivZdg6tXhEYpoAVOoapq24CBAjUS0A2R73GQ2sIECBAYAGBZJ5L5VjAx1sECMwn4E/HfDLOEyBAoH0CwhztG1M9IkCAQGsF1q1b19q+6RgBAkMWyJS3Id9B9QQIECBQCwFhjloMg0YQIECAwFYFyrx6eyVsFUoBAgRmC6xYsWJ6etryHLNlnCFAgED7BIQ52jemekSAAIF2Clh8tJ3jqlcERiggI2yE2G5FgACBsQkIc4yN3o0JECBAYFECMzMz+TJ2UZcoTIAAgUrA8hwVhQMCBAi0W0CYo93jq3cECBBoj4B59e0ZSz0hQIAAAQIECAxNQJhjaLQqJkCAAIFBC2R2/aCrVB8BApMi4A/IpIy0fhIgMPECwhwT/xEAQIAAgSYIZOFAM1aaMFDaSKDWApLCaj08GkeAAIEBCQhzDAhSNQQIECAwZIGpqakh30H1BAgQIECAAAECjRcQ5mj8EOoAAQIEJkHAd7CTMMr6SGDYApLChi2sfgIECNRBQJijDqOgDQQIECBAgAABAgQIECBAgMAABIQ5BoCoCgIECBAYgYCvYUeA7BYEWi+QhX5a30cdJECAwIQLCHNM+AdA9wkQIECAAAECBAgQIECAQHsEhDnaM5Z6QoAAAQIECBAgQIAAAQIEJlxAmGPCPwC6T4AAAQIECBAgQIAAAQIE2iMgzNGesdQTAgQIECBAgAABAgQIECAw4QLCHBP+AdB9AgQIECBAgAABAgQIECDQHgFhjvaMpZ4QIECAAAECBAgQIECAAIEJFxDmmPAPgO4TIECAAAECBAgQIECAAIH2CAhztGcs9YQAAQIECBAgQIAAAQIECEy4gDDHhH8AdJ8AAQIECBAgQIAAAQIECLRHQJijPWOpJwQIEGi3wMaNG9vdQb0jQIAAAQIECBBYuoAwx9IN1UCAAAECBAgQIECAAAECBAjUQkCYoxbDoBEECBAgQIAAAQIECBAgQIDA0gWEOZZuqAYCBAgQIECAAAECBAgQIECgFgLCHLUYBo0gQIAAAQIECBAgQIAAAQIEli4gzLF0QzUQIECAAAECBAgQIECAAAECtRAQ5qjFMGgEAQIECBAgQIAAAQIECBAgsHQBYY6lG6qBAAECBAgQIECAAAECBAgQqIWAMEcthkEjCBAgQIAAAQIECBAgQIAAgaULCHMs3VANBAgQIECAAAECBAgQIECAQC0EhDlqMQwaQYAAAQIECBAgQIAAAQIECCxdQJhj6YZqIECAAAECBAgQIECAAAECBGohIMxRi2HQCAIECBAgQIAAAQIECBAgQGDpAsIcSzdUAwECBAgQIECAAAECBAgQIFALAWGOWgyDRhAgQIAAAQIECBAgQIAAAQJLFxDmWLqhGggQIECAAAECBAgQIECAAIFaCAhz1GIYNIIAAQIEtiowMzOz1TIKECBAgAABAgQITLiAMMeEfwB0nwABAgQIECBAgAABAgQItEdAmKM9Y6knBAgQIECAAAECBAgQIEBgwgWEOSb8A6D7BAgQIECAAAECBAgQIECgPQLCHO0ZSz0hQIAAAQIECBBYWGDFihULF/AuAQIECDRdQJij6SOo/QQIECBAYGACW7ZsOf7441/96lf/8pe/XKDSX/3qV695zWvWrVv3wx/+cIFi3iJAgAABAgQIjF5g29Hf0h0JECBAgACBegqcfvrpL37xi9O2HXfc8elPf/p8jTzzzDP/7d/+Le9m+5v3vve98xVzngABAgQIECAwegHZHKM3d0cCBAgQIFBTga985SulZT/60Y8WaOIuu+xS3j377LOT2bFASW8RIECAAAECBEYsIMwxYnC3I0CAAAEC9RU45ZRTSuP222+/BVq55557Vu9effXV1bEDAgQIECBAgMDYBYQ5xj4EGkCAAAECBGoh8M1vfvMb3/hGmrLXXnt1BjJmN64zg+Oyyy6bXSBnsszH97///Ysuuug3v/nNnAWcJECAAAECBAgMQ8DaHMNQVScBAgQIEGiewKmnnloafdBBBy3c+iuuuKIqcIMb3KA6zkECJR/96Ec//vGPf/azLine truncated
|
||||
}
|
||||
},
|
||||
"cell_type": "markdown",
|
||||
@@ -22,7 +22,7 @@
|
||||
"\n",
|
||||
"To do this, let's build a simple research assistant! The graph will look something like the following:\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"This notebook is inspired by the paper [AutoGen: Enabling Next-Gen LLM Applications via Multi-Agent Conversation](https://arxiv.org/abs/2308.08155), by Wu, et. al. In the rest of this notebook, you will:\n",
|
||||
"\n",
|
||||
@@ -49,12 +49,12 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"%%capture --no-stderr\n",
|
||||
"%pip install -U langgraph langchain langchain_openai langchain_experimental"
|
||||
"%pip install -U langgraph langchain_community langchain_anthropic langchain_experimental"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"execution_count": null,
|
||||
"id": "30c2f3de-c730-4aec-85a6-af2c2f058803",
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
@@ -108,7 +108,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 16,
|
||||
"execution_count": null,
|
||||
"id": "4024eb89-843d-4cc3-ab3f-e1eb4d031179",
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
@@ -155,7 +155,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 17,
|
||||
"execution_count": 4,
|
||||
"id": "f20a18ca-2709-4c12-84f3-88678591a9fa",
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
@@ -190,7 +190,7 @@
|
||||
"\n",
|
||||
"@tool\n",
|
||||
"def read_document(\n",
|
||||
" file_name: Annotated[str, \"File path to save the document.\"],\n",
|
||||
" file_name: Annotated[str, \"File path to read the document from.\"],\n",
|
||||
" start: Annotated[Optional[int], \"The start line. Default is 0\"] = None,\n",
|
||||
" end: Annotated[Optional[int], \"The end line. Default is None\"] = None,\n",
|
||||
") -> str:\n",
|
||||
@@ -246,7 +246,7 @@
|
||||
"\n",
|
||||
"\n",
|
||||
"@tool\n",
|
||||
"def python_repl(\n",
|
||||
"def python_repl_tool(\n",
|
||||
" code: Annotated[str, \"The python code to execute to generate your chart.\"],\n",
|
||||
"):\n",
|
||||
" \"\"\"Use this to execute python code. If you want to see the output of a value,\n",
|
||||
@@ -275,7 +275,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 18,
|
||||
"execution_count": 5,
|
||||
"id": "e09fb60f-1aac-455b-b67d-8d2e4ccfd747",
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
@@ -285,68 +285,47 @@
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from typing import List, Optional\n",
|
||||
"from langchain.output_parsers.openai_functions import JsonOutputFunctionsParser\n",
|
||||
"from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder\n",
|
||||
"from langchain_openai import ChatOpenAI\n",
|
||||
"from typing import List, Optional, Literal\n",
|
||||
"from langchain_core.language_models.chat_models import BaseChatModel\n",
|
||||
"\n",
|
||||
"from langgraph.graph import END, StateGraph, START\n",
|
||||
"from langgraph.graph import StateGraph, MessagesState, START, END\n",
|
||||
"from langchain_core.messages import HumanMessage, trim_messages\n",
|
||||
"\n",
|
||||
"llm = ChatOpenAI(model=\"gpt-4o-mini\")\n",
|
||||
"\n",
|
||||
"trimmer = trim_messages(\n",
|
||||
" max_tokens=100000,\n",
|
||||
" strategy=\"last\",\n",
|
||||
" token_counter=llm,\n",
|
||||
" include_system=True,\n",
|
||||
")\n",
|
||||
"# The agent state is the input to each node in the graph\n",
|
||||
"class AgentState(MessagesState):\n",
|
||||
" # The 'next' field indicates where to route to next\n",
|
||||
" next: str\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def agent_node(state, agent, name):\n",
|
||||
" result = agent.invoke(state)\n",
|
||||
" return {\n",
|
||||
" \"messages\": [HumanMessage(content=result[\"messages\"][-1].content, name=name)]\n",
|
||||
" }\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def create_team_supervisor(llm: ChatOpenAI, system_prompt, members) -> str:\n",
|
||||
" \"\"\"An LLM-based router.\"\"\"\n",
|
||||
"def make_supervisor_node(llm: BaseChatModel, members: list[str]) -> str:\n",
|
||||
" options = [\"FINISH\"] + members\n",
|
||||
" function_def = {\n",
|
||||
" \"name\": \"route\",\n",
|
||||
" \"description\": \"Select the next role.\",\n",
|
||||
" \"parameters\": {\n",
|
||||
" \"title\": \"routeSchema\",\n",
|
||||
" \"type\": \"object\",\n",
|
||||
" \"properties\": {\n",
|
||||
" \"next\": {\n",
|
||||
" \"title\": \"Next\",\n",
|
||||
" \"anyOf\": [\n",
|
||||
" {\"enum\": options},\n",
|
||||
" ],\n",
|
||||
" },\n",
|
||||
" },\n",
|
||||
" \"required\": [\"next\"],\n",
|
||||
" },\n",
|
||||
" }\n",
|
||||
" prompt = ChatPromptTemplate.from_messages(\n",
|
||||
" [\n",
|
||||
" (\"system\", system_prompt),\n",
|
||||
" MessagesPlaceholder(variable_name=\"messages\"),\n",
|
||||
" (\n",
|
||||
" \"system\",\n",
|
||||
" \"Given the conversation above, who should act next?\"\n",
|
||||
" \" Or should we FINISH? Select one of: {options}\",\n",
|
||||
" ),\n",
|
||||
" ]\n",
|
||||
" ).partial(options=str(options), team_members=\", \".join(members))\n",
|
||||
" return (\n",
|
||||
" prompt\n",
|
||||
" | trimmer\n",
|
||||
" | llm.bind_functions(functions=[function_def], function_call=\"route\")\n",
|
||||
" | JsonOutputFunctionsParser()\n",
|
||||
" )"
|
||||
" system_prompt = (\n",
|
||||
" \"You are a supervisor tasked with managing a conversation between the\"\n",
|
||||
" f\" following workers: {members}. Given the following user request,\"\n",
|
||||
" \" respond with the worker to act next. Each worker will perform a\"\n",
|
||||
" \" task and respond with their results and status. When finished,\"\n",
|
||||
" \" respond with FINISH.\"\n",
|
||||
" )\n",
|
||||
"\n",
|
||||
" class Router(TypedDict):\n",
|
||||
" \"\"\"Worker to route to next. If no workers needed, route to FINISH.\"\"\"\n",
|
||||
"\n",
|
||||
" next: Literal[*options]\n",
|
||||
"\n",
|
||||
" def supervisor_node(state: MessagesState) -> MessagesState:\n",
|
||||
" \"\"\"An LLM-based router.\"\"\"\n",
|
||||
" messages = [\n",
|
||||
" {\"role\": \"system\", \"content\": system_prompt},\n",
|
||||
" ] + state[\"messages\"]\n",
|
||||
" response = llm.with_structured_output(Router).invoke(messages)\n",
|
||||
" next_ = response[\"next\"]\n",
|
||||
" if next_ == \"FINISH\":\n",
|
||||
" next_ = END\n",
|
||||
"\n",
|
||||
" return {\"next\": next_}\n",
|
||||
"\n",
|
||||
" return supervisor_node"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -365,7 +344,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 19,
|
||||
"execution_count": 6,
|
||||
"id": "53db0c78-e357-48ba-ae5f-3fc04735a3b7",
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
@@ -375,43 +354,37 @@
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import functools\n",
|
||||
"import operator\n",
|
||||
"\n",
|
||||
"from langchain_core.messages import BaseMessage, HumanMessage\n",
|
||||
"from langchain_openai.chat_models import ChatOpenAI\n",
|
||||
"from langchain_core.messages import HumanMessage\n",
|
||||
"from langchain_openai import ChatOpenAI\n",
|
||||
"from langgraph.prebuilt import create_react_agent\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# ResearchTeam graph state\n",
|
||||
"class ResearchTeamState(TypedDict):\n",
|
||||
" # A message is added after each team member finishes\n",
|
||||
" messages: Annotated[List[BaseMessage], operator.add]\n",
|
||||
" # The team members are tracked so they are aware of\n",
|
||||
" # the others' skill-sets\n",
|
||||
" team_members: List[str]\n",
|
||||
" # Used to route work. The supervisor calls a function\n",
|
||||
" # that will update this every time it makes a decision\n",
|
||||
" next: str\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"llm = ChatOpenAI(model=\"gpt-4o\")\n",
|
||||
"\n",
|
||||
"search_agent = create_react_agent(llm, tools=[tavily_tool])\n",
|
||||
"search_node = functools.partial(agent_node, agent=search_agent, name=\"Search\")\n",
|
||||
"\n",
|
||||
"research_agent = create_react_agent(llm, tools=[scrape_webpages])\n",
|
||||
"research_node = functools.partial(agent_node, agent=research_agent, name=\"WebScraper\")\n",
|
||||
"\n",
|
||||
"supervisor_agent = create_team_supervisor(\n",
|
||||
" llm,\n",
|
||||
" \"You are a supervisor tasked with managing a conversation between the\"\n",
|
||||
" \" following workers: Search, WebScraper. Given the following user request,\"\n",
|
||||
" \" respond with the worker to act next. Each worker will perform a\"\n",
|
||||
" \" task and respond with their results and status. When finished,\"\n",
|
||||
" \" respond with FINISH.\",\n",
|
||||
" [\"Search\", \"WebScraper\"],\n",
|
||||
")"
|
||||
"def search_node(state: AgentState) -> AgentState:\n",
|
||||
" result = search_agent.invoke(state)\n",
|
||||
" return {\n",
|
||||
" \"messages\": [\n",
|
||||
" HumanMessage(content=result[\"messages\"][-1].content, name=\"search\")\n",
|
||||
" ]\n",
|
||||
" }\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"web_scraper_agent = create_react_agent(llm, tools=[scrape_webpages])\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def web_scraper_node(state: AgentState) -> AgentState:\n",
|
||||
" result = web_scraper_agent.invoke(state)\n",
|
||||
" return {\n",
|
||||
" \"messages\": [\n",
|
||||
" HumanMessage(content=result[\"messages\"][-1].content, name=\"web_scraper\")\n",
|
||||
" ]\n",
|
||||
" }\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"research_supervisor_node = make_supervisor_node(llm, [\"search\", \"web_scraper\"])"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -424,7 +397,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 20,
|
||||
"execution_count": 7,
|
||||
"id": "1a7a1260-d9f6-4011-b2b1-13fab5126997",
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
@@ -434,41 +407,25 @@
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"research_graph = StateGraph(ResearchTeamState)\n",
|
||||
"research_graph.add_node(\"Search\", search_node)\n",
|
||||
"research_graph.add_node(\"WebScraper\", research_node)\n",
|
||||
"research_graph.add_node(\"supervisor\", supervisor_agent)\n",
|
||||
"research_builder = StateGraph(MessagesState)\n",
|
||||
"research_builder.add_node(\"supervisor\", research_supervisor_node)\n",
|
||||
"research_builder.add_node(\"search\", search_node)\n",
|
||||
"research_builder.add_node(\"web_scraper\", web_scraper_node)\n",
|
||||
"\n",
|
||||
"# Define the control flow\n",
|
||||
"research_graph.add_edge(\"Search\", \"supervisor\")\n",
|
||||
"research_graph.add_edge(\"WebScraper\", \"supervisor\")\n",
|
||||
"research_graph.add_conditional_edges(\n",
|
||||
" \"supervisor\",\n",
|
||||
" lambda x: x[\"next\"],\n",
|
||||
" {\"Search\": \"Search\", \"WebScraper\": \"WebScraper\", \"FINISH\": END},\n",
|
||||
")\n",
|
||||
"research_builder.add_edge(START, \"supervisor\")\n",
|
||||
"# We want our workers to ALWAYS \"report back\" to the supervisor when done\n",
|
||||
"research_builder.add_edge(\"search\", \"supervisor\")\n",
|
||||
"research_builder.add_edge(\"web_scraper\", \"supervisor\")\n",
|
||||
"# Add the edges where routing applies\n",
|
||||
"research_builder.add_conditional_edges(\"supervisor\", lambda state: state[\"next\"])\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"research_graph.add_edge(START, \"supervisor\")\n",
|
||||
"chain = research_graph.compile()\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# The following functions interoperate between the top level graph state\n",
|
||||
"# and the state of the research sub-graph\n",
|
||||
"# this makes it so that the states of each graph don't get intermixed\n",
|
||||
"def enter_chain(message: str):\n",
|
||||
" results = {\n",
|
||||
" \"messages\": [HumanMessage(content=message)],\n",
|
||||
" }\n",
|
||||
" return results\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"research_chain = enter_chain | chain"
|
||||
"research_graph = research_builder.compile()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 21,
|
||||
"execution_count": 8,
|
||||
"id": "110f59bed6134685",
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
@@ -479,7 +436,7 @@
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"image/jpeg": "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 truncated
|
||||
"image/png": "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 truncated
|
||||
"text/plain": [
|
||||
"<IPython.core.display.Image object>"
|
||||
]
|
||||
@@ -491,7 +448,7 @@
|
||||
"source": [
|
||||
"from IPython.display import Image, display\n",
|
||||
"\n",
|
||||
"display(Image(chain.get_graph(xray=True).draw_mermaid_png()))"
|
||||
"display(Image(research_graph.get_graph().draw_mermaid_png()))"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -504,7 +461,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 7,
|
||||
"execution_count": 9,
|
||||
"id": "912b0604-a178-4246-a36f-2dedae606680",
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
@@ -517,22 +474,26 @@
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"{'supervisor': {'next': 'Search'}}\n",
|
||||
"{'supervisor': {'next': 'search'}}\n",
|
||||
"---\n",
|
||||
"{'Search': {'messages': [HumanMessage(content='Taylor Swift\\'s next tour is called \"The Eras Tour,\" which is scheduled to hit U.S. stadiums beginning in March 2023 and running into August, with international dates set to be revealed later. The tour has already started with some shows, including the kickoff on March 18, 2023, in Glendale, AZ. The U.S. leg is set to wrap up in Los Angeles at SoFi Stadium on August 9, 2023.\\n\\nFor specific dates and locations, you may want to check Taylor Swift\\'s official website or trusted ticketing platforms, as the tour dates and details are subject to change.', name='Search')]}}\n",
|
||||
"{'search': {'messages': [HumanMessage(content=\"Taylor Swift's next tour is The Eras Tour, which includes both U.S. and international dates. She announced additional U.S. dates for 2024. You can find more details about the tour and ticket information on platforms like Ticketmaster and official announcements.\", additional_kwargs={}, response_metadata={}, name='search', id='4df8687b-50a8-4342-aad5-680732c4a10f')]}}\n",
|
||||
"---\n",
|
||||
"{'supervisor': {'next': 'FINISH'}}\n",
|
||||
"{'supervisor': {'next': 'web_scraper'}}\n",
|
||||
"---\n",
|
||||
"{'web_scraper': {'messages': [HumanMessage(content='Taylor Swift\\'s next tour is \"The Eras Tour.\" Here are some of the upcoming international dates for 2024 that were listed on Ticketmaster:\\n\\n1. **Toronto, ON, Canada** at Rogers Centre\\n - November 21, 2024\\n - November 22, 2024\\n - November 23, 2024\\n\\n2. **Vancouver, BC, Canada** at BC Place\\n - December 6, 2024\\n - December 7, 2024\\n - December 8, 2024\\n\\nFor the most current information and additional dates, you can check platforms like Ticketmaster or Taylor Swift\\'s [official website](https://www.taylorswift.com/events).', additional_kwargs={}, response_metadata={}, name='web_scraper', id='27524ebc-d179-4733-831d-ee10a58a2528')]}}\n",
|
||||
"---\n",
|
||||
"{'supervisor': {'next': '__end__'}}\n",
|
||||
"---\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"for s in research_chain.stream(\n",
|
||||
" \"when is Taylor Swift's next tour?\", {\"recursion_limit\": 100}\n",
|
||||
"for s in research_graph.stream(\n",
|
||||
" {\"messages\": [(\"user\", \"when is Taylor Swift's next tour?\")]},\n",
|
||||
" {\"recursion_limit\": 100},\n",
|
||||
"):\n",
|
||||
" if \"__end__\" not in s:\n",
|
||||
" print(s)\n",
|
||||
" print(\"---\")"
|
||||
" print(s)\n",
|
||||
" print(\"---\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -549,7 +510,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 22,
|
||||
"execution_count": 10,
|
||||
"id": "1bcdbf44-9481-430c-8429-fa142ed8a626",
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
@@ -559,75 +520,62 @@
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import operator\n",
|
||||
"from pathlib import Path\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Document writing team graph state\n",
|
||||
"class DocWritingState(TypedDict):\n",
|
||||
" # This tracks the team's conversation internally\n",
|
||||
" messages: Annotated[List[BaseMessage], operator.add]\n",
|
||||
" # This provides each worker with context on the others' skill sets\n",
|
||||
" team_members: str\n",
|
||||
" # This is how the supervisor tells langgraph who to work next\n",
|
||||
" next: str\n",
|
||||
" # This tracks the shared directory state\n",
|
||||
" current_files: str\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# This will be run before each worker agent begins work\n",
|
||||
"# It makes it so they are more aware of the current state\n",
|
||||
"# of the working directory.\n",
|
||||
"def prelude(state):\n",
|
||||
" written_files = []\n",
|
||||
" if not WORKING_DIRECTORY.exists():\n",
|
||||
" WORKING_DIRECTORY.mkdir()\n",
|
||||
" try:\n",
|
||||
" written_files = [\n",
|
||||
" f.relative_to(WORKING_DIRECTORY) for f in WORKING_DIRECTORY.rglob(\"*\")\n",
|
||||
" ]\n",
|
||||
" except Exception:\n",
|
||||
" pass\n",
|
||||
" if not written_files:\n",
|
||||
" return {**state, \"current_files\": \"No files written.\"}\n",
|
||||
" return {\n",
|
||||
" **state,\n",
|
||||
" \"current_files\": \"\\nBelow are files your team has written to the directory:\\n\"\n",
|
||||
" + \"\\n\".join([f\" - {f}\" for f in written_files]),\n",
|
||||
" }\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"llm = ChatOpenAI(model=\"gpt-4o\")\n",
|
||||
"\n",
|
||||
"doc_writer_agent = create_react_agent(\n",
|
||||
" llm, tools=[write_document, edit_document, read_document]\n",
|
||||
")\n",
|
||||
"# Injects current directory working state before each call\n",
|
||||
"context_aware_doc_writer_agent = prelude | doc_writer_agent\n",
|
||||
"doc_writing_node = functools.partial(\n",
|
||||
" agent_node, agent=context_aware_doc_writer_agent, name=\"DocWriter\"\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"note_taking_agent = create_react_agent(llm, tools=[create_outline, read_document])\n",
|
||||
"context_aware_note_taking_agent = prelude | note_taking_agent\n",
|
||||
"note_taking_node = functools.partial(\n",
|
||||
" agent_node, agent=context_aware_note_taking_agent, name=\"NoteTaker\"\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"chart_generating_agent = create_react_agent(llm, tools=[read_document, python_repl])\n",
|
||||
"context_aware_chart_generating_agent = prelude | chart_generating_agent\n",
|
||||
"chart_generating_node = functools.partial(\n",
|
||||
" agent_node, agent=context_aware_note_taking_agent, name=\"ChartGenerator\"\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"doc_writing_supervisor = create_team_supervisor(\n",
|
||||
" llm,\n",
|
||||
" \"You are a supervisor tasked with managing a conversation between the\"\n",
|
||||
" \" following workers: {team_members}. Given the following user request,\"\n",
|
||||
" \" respond with the worker to act next. Each worker will perform a\"\n",
|
||||
" \" task and respond with their results and status. When finished,\"\n",
|
||||
" \" respond with FINISH.\",\n",
|
||||
" [\"DocWriter\", \"NoteTaker\", \"ChartGenerator\"],\n",
|
||||
" tools=[write_document, edit_document, read_document],\n",
|
||||
" state_modifier=(\n",
|
||||
" \"You can read, write and edit documents based on note-taker's outlines. \"\n",
|
||||
" \"Don't ask follow-up questions.\"\n",
|
||||
" ),\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def doc_writing_node(state: AgentState) -> AgentState:\n",
|
||||
" result = doc_writer_agent.invoke(state)\n",
|
||||
" return {\n",
|
||||
" \"messages\": [\n",
|
||||
" HumanMessage(content=result[\"messages\"][-1].content, name=\"doc_writer\")\n",
|
||||
" ]\n",
|
||||
" }\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"note_taking_agent = create_react_agent(\n",
|
||||
" llm,\n",
|
||||
" tools=[create_outline, read_document],\n",
|
||||
" state_modifier=(\n",
|
||||
" \"You can read documents and create outlines for the document writer. \"\n",
|
||||
" \"Don't ask follow-up questions.\"\n",
|
||||
" ),\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def note_taking_node(state: AgentState) -> AgentState:\n",
|
||||
" result = note_taking_agent.invoke(state)\n",
|
||||
" return {\n",
|
||||
" \"messages\": [\n",
|
||||
" HumanMessage(content=result[\"messages\"][-1].content, name=\"note_taker\")\n",
|
||||
" ]\n",
|
||||
" }\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"chart_generating_agent = create_react_agent(\n",
|
||||
" llm, tools=[read_document, python_repl_tool]\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def chart_generating_node(state: AgentState) -> AgentState:\n",
|
||||
" result = chart_generating_agent.invoke(state)\n",
|
||||
" return {\n",
|
||||
" \"messages\": [\n",
|
||||
" HumanMessage(content=result[\"messages\"][-1].content, name=\"chart_generator\")\n",
|
||||
" ]\n",
|
||||
" }\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"doc_writing_supervisor_node = make_supervisor_node(\n",
|
||||
" llm, [\"doc_writer\", \"note_taker\", \"chart_generator\"]\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
@@ -641,7 +589,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 23,
|
||||
"execution_count": 11,
|
||||
"id": "9c5c644f-8966-4d2e-98d2-80d73520e9fe",
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
@@ -651,56 +599,28 @@
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Create the graph here:\n",
|
||||
"# Note that we have unrolled the loop for the sake of this doc\n",
|
||||
"authoring_graph = StateGraph(DocWritingState)\n",
|
||||
"authoring_graph.add_node(\"DocWriter\", doc_writing_node)\n",
|
||||
"authoring_graph.add_node(\"NoteTaker\", note_taking_node)\n",
|
||||
"authoring_graph.add_node(\"ChartGenerator\", chart_generating_node)\n",
|
||||
"authoring_graph.add_node(\"supervisor\", doc_writing_supervisor)\n",
|
||||
"\n",
|
||||
"# Add the edges that always occur\n",
|
||||
"authoring_graph.add_edge(\"DocWriter\", \"supervisor\")\n",
|
||||
"authoring_graph.add_edge(\"NoteTaker\", \"supervisor\")\n",
|
||||
"authoring_graph.add_edge(\"ChartGenerator\", \"supervisor\")\n",
|
||||
"# Create the graph here\n",
|
||||
"paper_writing_builder = StateGraph(AgentState)\n",
|
||||
"paper_writing_builder.add_node(\"supervisor\", doc_writing_supervisor_node)\n",
|
||||
"paper_writing_builder.add_node(\"doc_writer\", doc_writing_node)\n",
|
||||
"paper_writing_builder.add_node(\"note_taker\", note_taking_node)\n",
|
||||
"paper_writing_builder.add_node(\"chart_generator\", chart_generating_node)\n",
|
||||
"\n",
|
||||
"# Define the control flow\n",
|
||||
"paper_writing_builder.add_edge(START, \"supervisor\")\n",
|
||||
"# We want our workers to ALWAYS \"report back\" to the supervisor when done\n",
|
||||
"paper_writing_builder.add_edge(\"doc_writer\", \"supervisor\")\n",
|
||||
"paper_writing_builder.add_edge(\"note_taker\", \"supervisor\")\n",
|
||||
"paper_writing_builder.add_edge(\"chart_generator\", \"supervisor\")\n",
|
||||
"# Add the edges where routing applies\n",
|
||||
"authoring_graph.add_conditional_edges(\n",
|
||||
" \"supervisor\",\n",
|
||||
" lambda x: x[\"next\"],\n",
|
||||
" {\n",
|
||||
" \"DocWriter\": \"DocWriter\",\n",
|
||||
" \"NoteTaker\": \"NoteTaker\",\n",
|
||||
" \"ChartGenerator\": \"ChartGenerator\",\n",
|
||||
" \"FINISH\": END,\n",
|
||||
" },\n",
|
||||
")\n",
|
||||
"paper_writing_builder.add_conditional_edges(\"supervisor\", lambda state: state[\"next\"])\n",
|
||||
"\n",
|
||||
"authoring_graph.add_edge(START, \"supervisor\")\n",
|
||||
"chain = authoring_graph.compile()\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# The following functions interoperate between the top level graph state\n",
|
||||
"# and the state of the research sub-graph\n",
|
||||
"# this makes it so that the states of each graph don't get intermixed\n",
|
||||
"def enter_chain(message: str, members: List[str]):\n",
|
||||
" results = {\n",
|
||||
" \"messages\": [HumanMessage(content=message)],\n",
|
||||
" \"team_members\": \", \".join(members),\n",
|
||||
" }\n",
|
||||
" return results\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# We reuse the enter/exit functions to wrap the graph\n",
|
||||
"authoring_chain = (\n",
|
||||
" functools.partial(enter_chain, members=authoring_graph.nodes)\n",
|
||||
" | authoring_graph.compile()\n",
|
||||
")"
|
||||
"paper_writing_graph = paper_writing_builder.compile()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 24,
|
||||
"execution_count": 12,
|
||||
"id": "58e7d1e48a9c39a5",
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
@@ -711,7 +631,7 @@
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"image/jpeg": "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ERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQFzzjyAeHY3c1v+esN1cN/wDnOr0XQ1zzjzt5uxuSB5aw3cN/+dKqDoaIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiKA1BqWbH22Y/HVGX8m+PtiyWUxRRR7kBz3hriNyCAACTse4Akd0UVWk8tIn0VJOc1hudqGEI9W9ub+Gvzy7rD8Qwfyub+GtPotesbwty7oqR5d1h+IYP5XN/DTy7rD8Qwfyub+GnotesbwXLuvI/hy+E/PwUkw2m59HS5ahkzUyUWWF0RMEla4yV8HIYnddomelv07Tfbp19A+XdYfiGD+Vzfw1yvwieCmS8I/R1TA5uviaLqlxluC7WsSOlj26PYN4u57SQfz7HrtsnotesbwXOieD9xXv8beGWP1jd027S8WRe91Sm+34y6SAHlEpPIzl5nB2w2PQA7+l06Oue4ifUuBxNLGY/EYGrQpQMrV4I7c3LHGxoa1o+x9wAAW35d1h+IYP5XN/DT0WvWN4Ll3RUjy7rD8Qwfyub+Gnl3WH4hg/lc38NPRa9Y3guXdFSPLusPxDB/K5v4a/RqDV0R534rDTtHUxx3pWOI/MTERv+n+sd6ei16xvBcuyLQwmZr57HR3Kwe1ri5jo5W8r43tJDmOHqIIIPq6dCRsVvrJVE0zMTmgiIoCIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgKik78R8/+ahRG/wDtWf8A1/Sr0qIf5R9QfqFH96wtvDff9nzh1GUphERezkREQEREBEUS3VWLfqqTTYsk5qOk3IOrdk/YQOe6MP59uX4zXDbffpvtsoJZERUEREGnw4P2DPj1DLz7D+hh/wDNW9VDhx9p1B+15/3WK3rNxXrqnVWYiIsrkREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBUQ/wAo+oP1Cj+9YV7VEP8AKPqD9Qo/vWFt4b7/ALPnDqMpTC8+eECc5pnW1PVuVyWpIuGtLHsZaOl8ka0uOsiYl1qeEbGxEWFjSPS5Q1x5DvuvQao2teCWi+IuahyuosL5SuRxMr+lamZFLEx5e1ksTXhkrQ5zjs9rh1K9KovjBy5bd1Line truncated
|
||||
"image/png": "iVBORw0KGgoAAAANSUhEUgAAAmQAAAD5CAIAAAAGFL5fAAAAAXNSR0IArs4c6QAAIABJREFUeJzs3XdYU9f7APCTASSEvaeggkwHLpQpwwUOtAquChX3qnVUq2id1L23FfUr4sINKqI4ABUUFwiyRBDZSSAkQPbvj9sftco2cJPwfp4+fcjNHa+5N3nvOfcMglgsRgAAAABoHBHvAAAAAABpB8kSAAAAaAYkSwAAAKAZkCwBAACAZkCyBAAAAJoByRIAAABoBhnvAACQXkKBuKygjlMtrKkWiASIxxXhHVGLKFKIFBpRWZWsqkHW1FfEOxwA5AEB+lkC8A0eV5T5svpjKvtLTq1hVypFmaisSlbXVeDVykayFInE1QxBTbVAiUoq/8Ltak/r1pNm1I2Kd1wAyDBIlgD8R9Id+sdUtmFXareeKl2slfEO50dVlvPy0jj0Eh6bKXAara1nSsE7IgBkEiRLAP6R87Y6Nrysn7fmwOFaeMcieZ+zap7eoht2o7iN08U7FgBkDyRLABBC6PltOoclcP9Jl6wgz63e8t5znlwtn/x7F0Ulef5nAiBxkCwBQEl36EQSYcAwOSxQfo9F55/fXjBjU1cFRciXALQUJEvQ2d07W6Kuq+A4QhvvQDrUidUfp602o6qQ8A4EANkAt5agU3sVx6SpkztbpkQITVnV5fz2AryjAEBmQLIEnVdBJqeaIXAeo4N3IDigqZGH/az/8FIp3oEAIBsgWYLO68nVil6u6nhHgRsTS2UWQ1DwoQbvQACQAZAsQSeVkcwyMKN08gFunEfrJN6qwDsKAGQAJEvQSeW8YTuP6XSPKr+hY6xkZqWc+46NdyAASDtIlqAzKv5Uy60RUVU6aGzk4uLioqIivDZvmq6pUvZrSJYANAOSJeiM8tI4Xe1pHXOswsLCMWPGpKen47J5s7ra0/LSOO20cwDkBiRL0BnRi3jdenVQshQIBG3rzYxt1ebNW4isQLRwUPmcBfkSgKbAoASgMzq8LGfO9u4kEkGyu62rq9u6deuTJ08QQg4ODsuXLxeLxWPGjKlfYdSoUevXr+fxeCdOnIiJiSktLdXR0fH19Z0zZw6JREII+fv7d+/evXv37hcuXKirqzt16tTkyZO/2VyyMSOEHkWWaRso9XTpvA2DAWgWzGcJOp1ajlCRQpR4pkQInTp1Kioqau7cuTo6OlFRUVQqVVlZefPmzSEhIXPnzu3fv7+WlhZCiEQiJSUlubm5mZiYZGZmhoWFqampTZs2DdvJs2fP6urq9uzZU1NTY2Zm9v3mEkdTI3NYgvbYMwByA5Il6HRqqgTK6u1y5RcVFVGp1KCgIDKZ7Ofnhy20trZGCJmbm/fp0wdbQiKRzpw5QyD8k60LCwvj4uLqkyWZTA4NDaVSqY1tLnE0dXLxx9p22jkA8gGeWYJORygSU5Xb5cofOXJkXV3dokWLcnJyml6TwWBs3brVz8/P09MzNzeXTqfXv2Vvb1+fKTsGmUwgtEM5GwB5AskSdDo0NTKzjN8ee3Zyctq3bx+dTp80adLmzZsFgobrNul0+tSpU5OTk+fNm3fgwAEbGxuhUFj/bgdnSoRQdaWAQoWfAgCaAtWwoNOhqZFrqoUtWLEtnJycBg0adP78+T179hgaGgYHB3+/zpUrVxgMxunTpw0MDBBCBgYG+fn57RRPS3BYAnUtBRwDAED6we0k6IzMbZXZlZIvXPJ4PIQQkUicOnWqrq7uhw8fEEIUCgUhVF5eXr9aZWWlpqYmlimxl000Sv9+c4kjIKSmA/fNADQFviGgM1LVVPiYxunloiHZ3V64cOHx48c+Pj7l5eXl5eW2trYIIX19fWNj4/DwcCqVWlVVNWnSpP79+1+6dOnIkSO9e/eOi4tLTEwUiUSVlZUaGg3E8/3mSkpKkg37XUJV55x6BYCWg5Il6IzaadgaExMTHo+3Z8+e69evT5o06eeff0YIEQiE0NBQGo22c+fOW7duMRgMT0/PmTNnXr58ec2aNXw+//Tp0+bm5hcvXmxwn99vLtmY8zM4pj2UidDAB4AmwaAEoJO6eqDQb4ExkdjZk0RSDF1Vg2zrCCMSANAUqIYFnZSZDe35bbrTqEarH0eMGFFXV/f98l69er179+775erq6jdu3JB0mN86ePBgZGTk98tVVVWrq6sb3OT+/ftkcsPf9JpqQVoCK3hTV0mHCYC8gZIl6LyO//ExcJ2ZEpXU4LslJSUikajleyMSifVtdtpPVVUVh9O6CmRDQ8P6ARC+cT+i1NiCajNQTULRASC3IFmCzuvDC1ZVBd9xZCed1bKynPcsmj4yyBDvQACQAdDAB3Re1gPUOCzh+2dVeAeCjws7PntP0cc7CgBkAyRL0Kl5BuilP2d9Su90E1Rd2FngN99YQRF+AQBoEaiGBQBFnSiyHqhq0VsV70A6yMVdn0cE6atrK+IdCAAyA+4rAUCjZhllpbBfxTHxDqTdVZbzjv6e6/6TLmRKAFoFSpYA/ONlLCM9ieU0WseitwresUheTbXg6S06nyfynqIPta8AtBYkSwD+VVXBf3qrQiRCXayUu9rTVDTkoSNywYeakk+1qYksp9Ha0EsEgLaBZAnAt0ry6z68YOWlcZRVyPrmSsqqZJoaSUWDLGyvqUokTCQQVTMFnCqhGIlTE6qMLag9HFRtHCFNAtB2kCwBaFRZYV1ZAZdTJeCwhCQygV3Z8PyUbfbhwwdTU1MajSbZ3VKUiUrKJJo6SV1bwcyGRiJ39iH9APhxkCwBwE1QUNCyZct69uyJdyAAgGbAc34AAACgGZAsAQAAgGZAsgQAN6ampkQifAcBkAHwRQUAN58/f27VxCYAALxAsgQANyoqKo1NngUAkCqQLAHADZvNhuboAMgESJYA4EZTUxNKlgDIBEiWAOCGyWRCyRIAmQDJEgDcmJubQ2tYAGQCfFEBwM2nT5+gNSwAMgGSJQC4oVAoeIcAAGgRSJYA4Kaurg7vEAAALQLJEgAAAGgGJEsAcAMNfACQFfBFBQA30MAHAFkByRIAAABoBiRLAHCjqqoKI/gAIBMgWQKAm+rqahjBBwCZAMkSANyYmJhAAx8AZAJ8UQHATWFhITTwAUAmQLIEAAAAmgHJEgDcwOTPAMgKSJYA4AYmfwZAVkCyBAAAAJoByRIAAABoBiRLAHBjZmYGXUcAkAnwRQUAN/n5+dB1BACZAMkSAAAAaAYkSwAAAKAZkCwBwA30swRAVkCyBAA30M8SAFkByRIAAABoBiRLAHCjr68P1bAAyARIlgDgprS0FKphAZAJkCwBAACAZkCyBAA3ZDIZqmEBkAmQLAHAjUAggGpYAGQCJEsAcGNmZgYlSwBkAiRLAHCTn58PJUsAZAIkSwBwY2JiArOOACATCHBjC0AHGz58uKKiIoFAKC8vV1NTw/5WVFSMjIzEOzQAQMPIeAcAQKdDo9EKCgqwv+l0OvbH/PnzcQ0KANAUqAICoKMNGzbsmyVdunSZPHkyTuEAAJoHyRKAjjZx4sQuXbrUvySRSGPHjqVSqbgGBQBoCiRLADqatra2t7d3/UszM7MJEybgGhEAoBmQLAHAgb+/v5mZGVas9PX1pdFoeEcEAGgKJEsAcKCjo+Pp6UkgELp06QLFSgCkH7SGBZ0Ls4xXVcEXifCOAyFnh/EvHhe4urqW5iGEOHiHgyhUoo6JkqIS3EAD0ADoZwk6i5w37HfxlRyW0Ki7MqdKgHc4UkcsRsV5Nd17qwydoo93LABIHUiWoFPIfsNOe8rynGxIJMJYrE3JfcvKfcsat8AYPigAvgbJEsi/TxmclNjKYYHGeAciGz5ncrJTKsfOg48LgH/B8wkg/94+rnQaq4t3FDLD1IpGVVXIz8D/MSoA0gOSJZBzQoH4S06tioYi3oHIEkUqqbyQi3cUAEgRSJZAzlUzBAbmMDhO62jqKdawhXhHAYAUgWQJ5JwYIWj72lpCgVjAhdYMAPwLkiUAAADQDEiWAAAAQDMgWQIAAADNgGQJAAAANAOSJQAAANAMSJYAAABAMyBZAgAAAM2AZAkAAAA0A5IlAAAA0AxIlgAAAEAzIFkCAAAAzYBkCYBUEwgE06aPO3J0L96BANCpQbIEQKoRCARVVTUKhYJ3IAB0amS8AwAAILFYTCAQGnyLRCIdOXSmXQ8BAGgWlCwB+Nbz5wkzZgaM8HEOmjHx6rWLCKGXKUkeXv3T01Pr1xnp63L8xAGEUOSVCA+v/gcO7ZzgP2KEj/PSZXMzszLqVysuKVq7brnPKFe/8d6/r1z4ITMdW75v/7bxE4Y9ffpk2vRxHl79r9+47OHVPyr6Wv2Gp88cHzZicFbWBw+v/h5e/U+GHUYI1dXVbd2+foyf5xg/z5B1y0pKirGV792LDvxlwtDhgyZNGXU2/KRIJMKW/xLsv3HTH/87+7ffeG+fUa4CAUxVBkAbQbIE4D+4XO76jSsVFRSXLQ1xGuxGp5e3ZCs+j7dpw87Vf2yqrGIuXTanuKQIIUSnVyxaPINVXbVwwfI5sxfz+fxfl8zMy8vFNuFw2CdPHV7y66pNG3f6jZ1oaWF1Lza6foex92+7u3t36WK+aeNOMvmfGqCI86diYqIm/DRlzuzFLFYVlUpFCMXERP217U9LS+u1IaFD3IeGnTpyLuJU/X5evHj2IfN96OY9mzbuqt8PAKC14MsDwH9wOGwul+vq6jnUe2TLt5o7Z4mysrINQlY9bKdN97t27eL8eb+dDf9bU0Nr144jWJYa6u0zbbpf1O1rixYsRwjxeLzlS0NsbOyxPfj6jtu7b2tJSbGBgeH79++Kigr/WLmBQqG4OA+prz4tLimiUqlTJgeRyWRfHz+scvXvsEM9e/YJWb0ZIeTm6lldzbpw8cxP4ycrKysjhEhk8to1oVhaBQC0GZQsAfgPTU0tO7te4edOXrl6gcfjtXZzfX2DLl3MMz6kIYSSkhI/5uX4jHIdNmLwsBGLine truncated
|
||||
"text/plain": [
|
||||
"<IPython.core.display.Image object>"
|
||||
]
|
||||
@@ -723,12 +643,12 @@
|
||||
"source": [
|
||||
"from IPython.display import Image, display\n",
|
||||
"\n",
|
||||
"display(Image(chain.get_graph().draw_mermaid_png()))"
|
||||
"display(Image(paper_writing_graph.get_graph().draw_mermaid_png()))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 12,
|
||||
"execution_count": 14,
|
||||
"id": "9860fd46-c24d-40a5-a6ba-e8fddcd43369",
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
@@ -741,23 +661,33 @@
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"{'supervisor': {'next': 'NoteTaker'}}\n",
|
||||
"{'supervisor': {'next': 'note_taker'}}\n",
|
||||
"---\n",
|
||||
"{'NoteTaker': {'messages': [HumanMessage(content='The poem has been written and saved to \"poem.txt\".', name='NoteTaker')]}}\n",
|
||||
"{'note_taker': {'messages': [HumanMessage(content='The outline for the poem about cats has been created and saved as \"cats_poem_outline.txt\".', additional_kwargs={}, response_metadata={}, name='note_taker', id='14a5d8ca-9092-416f-96ee-ba16686e8658')]}}\n",
|
||||
"---\n",
|
||||
"{'supervisor': {'next': 'FINISH'}}\n",
|
||||
"{'supervisor': {'next': 'doc_writer'}}\n",
|
||||
"---\n",
|
||||
"{'doc_writer': {'messages': [HumanMessage(content='The poem about cats has been written and saved as \"cats_poem.txt\".', additional_kwargs={}, response_metadata={}, name='doc_writer', id='c4e31a94-63ae-4632-9e80-1166f3f138b2')]}}\n",
|
||||
"---\n",
|
||||
"{'supervisor': {'next': '__end__'}}\n",
|
||||
"---\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"for s in authoring_chain.stream(\n",
|
||||
" \"Write an outline for poem and then write the poem to disk.\",\n",
|
||||
"for s in paper_writing_graph.stream(\n",
|
||||
" {\n",
|
||||
" \"messages\": [\n",
|
||||
" (\n",
|
||||
" \"user\",\n",
|
||||
" \"Write an outline for poem about cats and then write the poem to disk.\",\n",
|
||||
" )\n",
|
||||
" ]\n",
|
||||
" },\n",
|
||||
" {\"recursion_limit\": 100},\n",
|
||||
"):\n",
|
||||
" if \"__end__\" not in s:\n",
|
||||
" print(s)\n",
|
||||
" print(\"---\")"
|
||||
" print(s)\n",
|
||||
" print(\"---\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -774,35 +704,21 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 13,
|
||||
"id": "95ae7e52-92ed-41a3-88c4-21b6d7c8b041",
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2024-05-15T08:19:55.454047Z",
|
||||
"start_time": "2024-05-15T08:19:53.725466Z"
|
||||
}
|
||||
},
|
||||
"execution_count": 15,
|
||||
"id": "8cbfbe34-43f5-4a3d-8e9b-6a1d9b339aec",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain_core.messages import BaseMessage\n",
|
||||
"from langchain_openai.chat_models import ChatOpenAI\n",
|
||||
"\n",
|
||||
"llm = ChatOpenAI(model=\"gpt-4o\")\n",
|
||||
"\n",
|
||||
"supervisor_node = create_team_supervisor(\n",
|
||||
" llm,\n",
|
||||
" \"You are a supervisor tasked with managing a conversation between the\"\n",
|
||||
" \" following teams: {team_members}. Given the following user request,\"\n",
|
||||
" \" respond with the worker to act next. Each worker will perform a\"\n",
|
||||
" \" task and respond with their results and status. When finished,\"\n",
|
||||
" \" respond with FINISH.\",\n",
|
||||
" [\"ResearchTeam\", \"PaperWritingTeam\"],\n",
|
||||
")"
|
||||
"teams_supervisor_node = make_supervisor_node(llm, [\"research_team\", \"writing_team\"])"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 25,
|
||||
"execution_count": 16,
|
||||
"id": "4880e573-612f-4d24-97c1-2079382a4a2f",
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
@@ -812,49 +728,43 @@
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Top-level graph state\n",
|
||||
"class State(TypedDict):\n",
|
||||
" messages: Annotated[List[BaseMessage], operator.add]\n",
|
||||
" next: str\n",
|
||||
"def call_research_team(state: AgentState) -> AgentState:\n",
|
||||
" response = research_graph.invoke({\"messages\": state[\"messages\"][-1]})\n",
|
||||
" return {\n",
|
||||
" \"messages\": [\n",
|
||||
" HumanMessage(content=response[\"messages\"][-1].content, name=\"research_team\")\n",
|
||||
" ]\n",
|
||||
" }\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def get_last_message(state: State) -> str:\n",
|
||||
" return state[\"messages\"][-1].content\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def join_graph(response: dict):\n",
|
||||
" return {\"messages\": [response[\"messages\"][-1]]}\n",
|
||||
"def call_paper_writing_team(state: AgentState) -> AgentState:\n",
|
||||
" response = paper_writing_graph.invoke({\"messages\": state[\"messages\"][-1]})\n",
|
||||
" return {\n",
|
||||
" \"messages\": [\n",
|
||||
" HumanMessage(content=response[\"messages\"][-1].content, name=\"writing_team\")\n",
|
||||
" ]\n",
|
||||
" }\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Define the graph.\n",
|
||||
"super_graph = StateGraph(State)\n",
|
||||
"# First add the nodes, which will do the work\n",
|
||||
"super_graph.add_node(\"ResearchTeam\", get_last_message | research_chain | join_graph)\n",
|
||||
"super_graph.add_node(\n",
|
||||
" \"PaperWritingTeam\", get_last_message | authoring_chain | join_graph\n",
|
||||
")\n",
|
||||
"super_graph.add_node(\"supervisor\", supervisor_node)\n",
|
||||
"super_builder = StateGraph(AgentState)\n",
|
||||
"super_builder.add_node(\"supervisor\", teams_supervisor_node)\n",
|
||||
"super_builder.add_node(\"research_team\", call_research_team)\n",
|
||||
"super_builder.add_node(\"writing_team\", call_paper_writing_team)\n",
|
||||
"\n",
|
||||
"# Define the graph connections, which controls how the logic\n",
|
||||
"# propagates through the program\n",
|
||||
"super_graph.add_edge(\"ResearchTeam\", \"supervisor\")\n",
|
||||
"super_graph.add_edge(\"PaperWritingTeam\", \"supervisor\")\n",
|
||||
"super_graph.add_conditional_edges(\n",
|
||||
" \"supervisor\",\n",
|
||||
" lambda x: x[\"next\"],\n",
|
||||
" {\n",
|
||||
" \"PaperWritingTeam\": \"PaperWritingTeam\",\n",
|
||||
" \"ResearchTeam\": \"ResearchTeam\",\n",
|
||||
" \"FINISH\": END,\n",
|
||||
" },\n",
|
||||
")\n",
|
||||
"super_graph.add_edge(START, \"supervisor\")\n",
|
||||
"super_graph = super_graph.compile()"
|
||||
"# Define the control flow\n",
|
||||
"super_builder.add_edge(START, \"supervisor\")\n",
|
||||
"# We want our teams to ALWAYS \"report back\" to the top-level supervisor when done\n",
|
||||
"super_builder.add_edge(\"research_team\", \"supervisor\")\n",
|
||||
"super_builder.add_edge(\"writing_team\", \"supervisor\")\n",
|
||||
"# Add the edges where routing applies\n",
|
||||
"super_builder.add_conditional_edges(\"supervisor\", lambda state: state[\"next\"])\n",
|
||||
"super_graph = super_builder.compile()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 26,
|
||||
"execution_count": 17,
|
||||
"id": "270ff3ae26cd42ff",
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
@@ -865,7 +775,7 @@
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"image/jpeg": "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NZLT2b0/ltNYqnjLkeXoPrskljj7NxhefRlbvHvzMJGzm/OtvICjdR4dufwluiXBkkjeaGQ7/wAlK0h0cg29bXta4fSApJee/dixtGzbndywV43SyOHqa0En/wBgtUTMVRNPNYeLSuYOodM4rJloY+3Vjmc0fkuc0Ej+47hSqgNAUJcZonCV52lk7akZkaRsWvI3cNvoJKn1u7FMXKop5YyTzERFyQREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREGK1Wiu1pa88bZoJWGOSN43a5pGxBHzEKuYrJHSzocNl5S2Fu0VDIyn0LDO5sb3HumHdsfjjZzdzztZaFht1IL9aSvahjs15W8r4pWBzHj5iD0IXWiuIjdq5Sqnai4HcPNXZmxls3ojAZfKWeXtrl3HRSyycrQ1vM5zSTs1oH6gFHHwbeFDg0HhvpYho2AOJg6Dv/o/SVYToCpAf+j8llsUzffsqt57ox+pknM1o+gAD6F+fAmx7VZ7/Gh/CW9y3PKvrH/phGr36V0bgdDYvybp3D0cHj+0MviuPrthj5ztu7laANzsOv0KZVX+BNj2qz3+ND+EnwJse1We/wAaH8JOHb/H8pMI1WhFqzGY7LW+Keo9PyapzHk6hh8ZehLZYe17Wea8yTm/k/i7Votug683U+q1/Amx7VZ7/Gh/CTh2/wAfykwjVl1jw40rxCZVZqfTmL1CyqXGBuTqMnERdtzFvMDtvyjfb5gq2PBu4UhhYOHGlwwkEt8kwbEjfY/F+k/vU/8AAmx7VZ7/ABofwkGibAPypzx//tD+EnDt/j+UmEasWk+FGieHtye/pvSuF09aliMUtjH0o67nR7hxa5zQOm4B2+hfVmZmvJGVau0mno3tfZtj4lxzSHNiiP5TNx6b/ikDkHNu/kyR8PsbK5rsjYv5rbqI8jbfJF/fECIz/e0qysY2Noa0BrQNgANgAm9RbzonGemH9/Rco5PpERedkREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERBr/Blvn71mATz/AAbwe49W3jOV29f6/UP1n1bAWv8AB7+frWfVu3wbwnQBvN/1nK9/r2/X079vWtgICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiINe4IDz/a1PO0n4NYP0QPSH+lZbqTt3H9fqPd69hLXuC28/8ArXqeb4NYPcco228ay3r9fr6fathICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIi/CQ0Ek7Ad5KD9RUp2sM1lgLGExtJ2Nd1hsX7L43zt9T2sbGdmnvBJ3I67DdfPl3WH5hg/e5vw17PC3PPCP1hcF3RUjy7rD8wwfvc34aeXdYfmGD97m/DTwtesdYMF3XmyUtmvjrUtKuy5cZE90FeSXsmyyAHlaX7HlBOw32O2++xVR8u6w/MMH73N+Gnl3WH5hg/e5vw08LXrHWDByFwx8PK3q/wlfIo4Z26mTz7sdp6eA5MOfR8XsWjLM4dgC4NbZcS0kbCI9RzFd6LmnTPg/TaW4/Z/ivVx+GOYytfsxUNiQRV5XbCaZh7LfmkAG//mf/AEum3/LusPzDB+9zfhp4WvWOsGC7oqR5d1h+YYP3ub8NPLusPzDB+9zfhp4WvWOsGC7oqR5d1h+YYP3ub8NPLusPzDB+9zfhp4WvWOsGC7oqjR1bk6duCLO0KteCxI2GO3RsOlY2RxAa17XMaWhxOwcCRvsDtuFblwuW6rc4VGGAiIuSCIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAo/UJLcBkiDsRWlII/8hUgo7UXyfyf7LL/AVuj78LHNV9JADSuGAAA8Sh6D/dtUsorSfyVw37FD/AFKr6lz79XqTzERFzQREQERfE0rYInyPPKxjS5x232A70H2ijNM6lx2sdP4/N4iwbeMvwtnrTmN0fOw9x5XAOH6iAVJoCIiCva8O2mpSO8WKxH0Ht41sRa7198mJv8Af1v8+NbEXPaPZUes/RryERF4GRERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAUdqL5P5P9ll/gKkVHai+T+T/ZZf4Ct0ffj1WOasaT+SuG/Yof4ApC2yaSpMyvIIZ3McI5HN5gx23QkevY+pR+k/krhv2KH+AKSmhZYhfFIOaN7S1w+cHvX1Ln36vUnm4xzOf1Jw94RcR8Hns7q6lxOqaeGRfanzL7NSzGJwx1yi9pBhHM4Ax7MLQQNj3ra3HPU+VxXEapVx+WuU67tEaguGGtZexhljFfspeVp25m7u5Xd43OxV5014Line truncated
|
||||
"image/png": "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 truncated
|
||||
"text/plain": [
|
||||
"<IPython.core.display.Image object>"
|
||||
]
|
||||
@@ -882,7 +792,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 16,
|
||||
"execution_count": 22,
|
||||
"id": "6b8badbf-d728-44bd-a2a7-5b4e587c92fe",
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
@@ -895,191 +805,19 @@
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"{'supervisor': {'next': 'ResearchTeam'}}\n",
|
||||
"{'supervisor': {'next': 'research_team'}}\n",
|
||||
"---\n",
|
||||
"{'ResearchTeam': {'messages': [HumanMessage(content=\"Unfortunately, the information obtained from the U.S. Fish & Wildlife Service web pages does not provide additional detailed information on the conservation status, size, or lifespan of the North American sturgeon species beyond what was already included in the initial research report. These pages primarily contain placeholders for the species' profiles without specific information on the topics of interest.\\n\\nBased on the information available, the research report provided earlier remains the most comprehensive summary of the North American sturgeon, including an overview of the species, their conservation status, size, lifespan, conservation efforts, and a chart summarizing key data for each species. Further details would require access to additional sources or in-depth research reports that are not currently available in the provided documents.\", name='WebScraper')]}}\n",
|
||||
"{'research_team': {'messages': [HumanMessage(content=\"**AI Agents Overview 2023**\\n\\nAI agents are sophisticated technologies that automate and enhance various processes across industries, becoming increasingly integral to business operations. In 2023, these agents are notable for their advanced capabilities in communication, data visualization, and language processing.\\n\\n**Popular AI Agents in 2023:**\\n1. **Auto GPT**: This agent is renowned for its seamless integration abilities, significantly impacting industries by improving communication and operational workflows.\\n2. **ChartGPT**: Specializing in data visualization, ChartGPT enables users to interact with data innovatively, providing deeper insights and comprehension.\\n3. **LLMops**: With advanced language capabilities, LLMops is a versatile tool seeing widespread use across multiple sectors.\\n\\n**Market Trends:**\\nThe AI agents market is experiencing rapid growth, with significant advancements anticipated by 2030. There's a growing demand for AI agents in personalized interactions, particularly within customer service, healthcare, and marketing sectors. This trend is fueled by the need for more efficient and tailored customer experiences.\\n\\n**Key Players:**\\nLeading companies such as Microsoft, IBM, Google, Oracle, and AWS are key players in the AI agents market, highlighting the widespread adoption and investment in these technologies.\\n\\n**Technological Innovations:**\\nAI agents are being developed alongside simulation technologies for robust testing and deployment environments. Innovations in generative AI are accelerating, supported by advancements in large language models and platforms like ChatGPT.\\n\\n**Applications in Healthcare:**\\nIn healthcare, AI agents are automating routine tasks, allowing medical professionals to focus more on patient care. They're poised to significantly enhance healthcare delivery and efficiency.\\n\\n**Future Prospects:**\\nThe future of AI agents is promising, with continued evolution and integration into various platforms and ecosystems, offering more seamless and intelligent interactions. As these technologies advance, they are expected to redefine business operations and customer interactions.\", additional_kwargs={}, response_metadata={}, name='research_team', id='5f6606e0-838c-406c-b50d-9f9f6a076322')]}}\n",
|
||||
"---\n",
|
||||
"{'supervisor': {'next': 'PaperWritingTeam'}}\n",
|
||||
"{'supervisor': {'next': 'writing_team'}}\n",
|
||||
"---\n",
|
||||
"{'PaperWritingTeam': {'messages': [HumanMessage(content=\"It appears that the information you were seeking from the U.S. Fish & Wildlife Service web pages was not as detailed as you needed for the North American sturgeon species. If you're looking for more comprehensive data on their conservation status, size, lifespan, and conservation efforts, you might need to consider exploring scientific journals, research papers, or contacting experts in the field.\\n\\nIf you have any specific questions or require assistance with creating an outline or reading a document related to the North American sturgeon, please let me know how I can assist you further.\", name='NoteTaker')]}}\n",
|
||||
"{'writing_team': {'messages': [HumanMessage(content=\"Here are the contents of the documents:\\n\\n### AI Agents Overview 2023\\n\\n**AI Agents Overview 2023**\\n\\nAI agents are sophisticated technologies that automate and enhance various processes across industries, becoming increasingly integral to business operations. In 2023, these agents are notable for their advanced capabilities in communication, data visualization, and language processing.\\n\\n**Popular AI Agents in 2023:**\\n1. **Auto GPT**: This agent is renowned for its seamless integration abilities, significantly impacting industries by improving communication and operational workflows.\\n2. **ChartGPT**: Specializing in data visualization, ChartGPT enables users to interact with data innovatively, providing deeper insights and comprehension.\\n3. **LLMops**: With advanced language capabilities, LLMops is a versatile tool seeing widespread use across multiple sectors.\\n\\n**Market Trends:**\\nThe AI agents market is experiencing rapid growth, with significant advancements anticipated by 2030. There's a growing demand for AI agents in personalized interactions, particularly within customer service, healthcare, and marketing sectors. This trend is fueled by the need for more efficient and tailored customer experiences.\\n\\n**Key Players:**\\nLeading companies such as Microsoft, IBM, Google, Oracle, and AWS are key players in the AI agents market, highlighting the widespread adoption and investment in these technologies.\\n\\n**Technological Innovations:**\\nAI agents are being developed alongside simulation technologies for robust testing and deployment environments. Innovations in generative AI are accelerating, supported by advancements in large language models and platforms like ChatGPT.\\n\\n**Applications in Healthcare:**\\nIn healthcare, AI agents are automating routine tasks, allowing medical professionals to focus more on patient care. They're poised to significantly enhance healthcare delivery and efficiency.\\n\\n**Future Prospects:**\\nThe future of AI agents is promising, with continued evolution and integration into various platforms and ecosystems, offering more seamless and intelligent interactions. As these technologies advance, they are expected to redefine business operations and customer interactions.\\n\\n### AI_Agents_Overview_2023_Outline\\n\\n1. Introduction to AI Agents in 2023\\n2. Popular AI Agents: Auto GPT, ChartGPT, LLMops\\n3. Market Trends and Growth\\n4. Key Players in the AI Agents Market\\n5. Technological Innovations: Simulation and Generative AI\\n6. Applications of AI Agents in Healthcare\\n7. Future Prospects of AI Agents\", additional_kwargs={}, response_metadata={}, name='writing_team', id='851bd8a6-740e-488c-8928-1f9e05e96ea0')]}}\n",
|
||||
"---\n",
|
||||
"{'supervisor': {'next': 'ResearchTeam'}}\n",
|
||||
"{'supervisor': {'next': 'writing_team'}}\n",
|
||||
"---\n",
|
||||
"{'ResearchTeam': {'messages': [HumanMessage(content='I\\'ve found several resources that could provide the comprehensive data you\\'re looking for on the conservation status, size, lifespan, and conservation efforts for North American sturgeon species:\\n\\n1. A paper titled \"Reconnecting Fragmented Sturgeon Populations in North American Rivers\" by Jager et al., which may contain information on distribution, range contraction, and conservation efforts ([Read the paper](https://web.ornl.gov/~zij/mypubs/sturgeon/Jager at al_2016_Reconnecting Fragmented Sturgeon Populations in North American Rivers_Fisheries.pdf)).\\n\\n2. The North American Sturgeon and Paddlefish Society (NASPS) website, which lists experts and provides details on the society\\'s mission to foster the conservation and restoration of sturgeon species in North America ([Visit NASPS](https://nasps-sturgeon.org/about/)).\\n\\n3. A press release from the U.S. Fish and Wildlife Service indicating that lake sturgeon do not require listing under the Endangered Species Act due to successful ongoing management efforts ([Read the press release](https://www.fws.gov/press-release/2024-04/collaborative-conservation-keeps-lake-sturgeon-endangered-list)).\\n\\n4. Information on the Conservation Genetics of Atlantic Sturgeon by the USGS, discussing genetic studies and management strategies for Atlantic Sturgeon populations ([Learn more from USGS](https://www.usgs.gov/centers/eesc/science/conservation-genetics-atlantic-sturgeon)).\\n\\n5. A story on restoring lake sturgeon along the Ontonagon River in Michigan and St. Louis River in Minnesota, detailing efforts by the Iron River National Fish Hatchery and Ashland Fish and Wildlife Conservation Office ([Read the story](https://www.fws.gov/story/2024-04/restoring-reverence-along-lake-sturgeon)).\\n\\nThese resources should provide a strong foundation for understanding the current state of North American sturgeon species. If you require more detailed summaries or have any other questions, feel free to ask.', name='Search')]}}\n",
|
||||
"{'writing_team': {'messages': [HumanMessage(content='The documents have been successfully created and saved:\\n\\n1. **AI_Agents_Overview_2023.txt** - Contains the detailed overview of AI agents in 2023.\\n2. **AI_Agents_Overview_2023_Outline.txt** - Contains the outline of the document.', additional_kwargs={}, response_metadata={}, name='writing_team', id='c87c0778-a085-4a8e-8ee1-9b43b9b0b143')]}}\n",
|
||||
"---\n",
|
||||
"{'supervisor': {'next': 'ResearchTeam'}}\n",
|
||||
"---\n",
|
||||
"{'ResearchTeam': {'messages': [HumanMessage(content='Based on the search results obtained, here is a summary of the information available regarding the conservation status, size, lifespan, and conservation efforts for North American sturgeon species:\\n\\n1. **Reconnecting Fragmented Sturgeon Populations in North American Rivers (Jager et al.)**:\\n - This paper discusses the fragmentation of large North American rivers by dams that interrupt the migrations of wide-ranging fishes like sturgeons. Efforts to reconnect habitats are viewed as crucial for protecting sturgeon species in U.S. rivers, as these species have lost between 5% and 60% of their historical ranges. [Learn more from the paper](https://www.semanticscholar.org/paper/Reconnecting-Fragmented-Sturgeon-Populations-in-Jager-Parsley/45414d7c86cd2d4f04b9490c7143f36b5158e729/figure/0).\\n\\n2. **North American Sturgeon and Paddlefish Society (NASPS)**:\\n - NASPS is dedicated to promoting the conservation and restoration of sturgeon species in North America by developing and advancing research pertaining to their biology, management, and utilization. [Visit NASPS](https://nasps-sturgeon.org/about/).\\n\\n3. **U.S. Fish and Wildlife Service on Lake Sturgeon and the Endangered Species Act**:\\n - The U.S. Fish and Wildlife Service determined that lake sturgeon do not require listing under the Endangered Species Act, thanks to ongoing management efforts such as fish stocking that have contributed to their population stability. [Read the press release](https://www.fws.gov/press-release/2024-04/collaborative-conservation-keeps-lake-sturgeon-endangered-list).\\n\\n4. **Conservation Genetics of Atlantic Sturgeon (USGS)**:\\n - The USGS is conducting research on the conservation genetics of Atlantic sturgeon, with a focus on genetic assignment testing and population genetic studies. This is in response to the rediscovery of populations that were previously thought to be extirpated, necessitating updated management strategies. [Learn more from USGS](https://www.usgs.gov/centers/eesc/science/conservation-genetics-atlantic-sturgeon).\\n\\n5. **Restoration Efforts Along the Ontonagon River and St. Louis River**:\\n - The Iron River National Fish Hatchery and Ashland Fish and Wildlife Conservation Office are working with partners to restore lake sturgeon in Michigan and Minnesota. Efforts include collecting larval sturgeon and milt for breeding and stocking programs. [Read the story](https://www.fws.gov/story/2024-04/restoring-reverence-along-lake-sturgeon).\\n\\nThese resources provide comprehensive data on the state of North American sturgeon species and the various conservation efforts being undertaken to preserve and enhance their populations. If you require further information or assistance, feel free to ask.', name='Search')]}}\n",
|
||||
"---\n",
|
||||
"{'supervisor': {'next': 'ResearchTeam'}}\n",
|
||||
"---\n",
|
||||
"{'ResearchTeam': {'messages': [HumanMessage(content='Based on the search results obtained, here is a summary of the information available regarding the conservation status, size, lifespan, and conservation efforts for North American sturgeon species:\\n\\n1. **Reconnecting Fragmented Sturgeon Populations in North American Rivers (Jager et al.)**:\\n - This paper discusses the fragmentation of large North American rivers by dams that interrupt the migrations of wide-ranging fishes like sturgeons. Efforts to reconnect habitats are viewed as crucial for protecting sturgeon species in U.S. rivers, as these species have lost between 5% and 60% of their historical ranges. [Learn more from the paper](https://www.semanticscholar.org/paper/Reconnecting-Fragmented-Sturgeon-Populations-in-Jager-Parsley/45414d7c86cd2d4f04b9490c7143f36b5158e729/figure/0).\\n\\n2. **North American Sturgeon and Paddlefish Society (NASPS)**:\\n - NASPS is dedicated to promoting the conservation and restoration of sturgeon species in North America by developing and advancing research pertaining to their biology, management, and utilization. [Visit NASPS](https://nasps-sturgeon.org/about/).\\n\\n3. **U.S. Fish and Wildlife Service on Lake Sturgeon and the Endangered Species Act**:\\n - The U.S. Fish and Wildlife Service determined that lake sturgeon do not require listing under the Endangered Species Act, thanks to ongoing management efforts such as fish stocking that have contributed to their population stability. [Read the press release](https://www.fws.gov/press-release/2024-04/collaborative-conservation-keeps-lake-sturgeon-endangered-list).\\n\\n4. **Conservation Genetics of Atlantic Sturgeon (USGS)**:\\n - The USGS is conducting research on the conservation genetics of Atlantic sturgeon, with a focus on genetic assignment testing and population genetic studies. This is in response to the rediscovery of populations that were previously thought to be extirpated, necessitating updated management strategies. [Learn more from USGS](https://www.usgs.gov/centers/eesc/science/conservation-genetics-atlantic-sturgeon).\\n\\n5. **Restoration Efforts Along the Ontonagon River and St. Louis River**:\\n - The Iron River National Fish Hatchery and Ashland Fish and Wildlife Conservation Office are working with partners to restore lake sturgeon in Michigan and Minnesota. Efforts include collecting larval sturgeon and milt for breeding and stocking programs. [Read the story](https://www.fws.gov/story/2024-04/restoring-reverence-along-lake-sturgeon).\\n\\nThese resources provide comprehensive data on the state of North American sturgeon species and the various conservation efforts being undertaken to preserve and enhance their populations. If you require further information or assistance, feel free to ask.')]}}\n",
|
||||
"---\n",
|
||||
"{'supervisor': {'next': 'ResearchTeam'}}\n",
|
||||
"---\n",
|
||||
"{'ResearchTeam': {'messages': [HumanMessage(content='Based on the search results obtained, here is a summary of the information available regarding the conservation status, size, lifespan, and conservation efforts for North American sturgeon species:\\n\\n1. **Reconnecting Fragmented Sturgeon Populations in North American Rivers (Jager et al.)**:\\n - This paper discusses the fragmentation of large North American rivers by dams that interrupt the migrations of wide-ranging fishes like sturgeons. Efforts to reconnect habitats are viewed as crucial for protecting sturgeon species in U.S. rivers, as these species have lost between 5% and 60% of their historical ranges. [Learn more from the paper](https://www.semanticscholar.org/paper/Reconnecting-Fragmented-Sturgeon-Populations-in-Jager-Parsley/45414d7c86cd2d4f04b9490c7143f36b5158e729/figure/0).\\n\\n2. **North American Sturgeon and Paddlefish Society (NASPS)**:\\n - NASPS is dedicated to promoting the conservation and restoration of sturgeon species in North America by developing and advancing research pertaining to their biology, management, and utilization. [Visit NASPS](https://nasps-sturgeon.org/about/).\\n\\n3. **U.S. Fish and Wildlife Service on Lake Sturgeon and the Endangered Species Act**:\\n - The U.S. Fish and Wildlife Service determined that lake sturgeon do not require listing under the Endangered Species Act, thanks to ongoing management efforts such as fish stocking that have contributed to their population stability. [Read the press release](https://www.fws.gov/press-release/2024-04/collaborative-conservation-keeps-lake-sturgeon-endangered-list).\\n\\n4. **Conservation Genetics of Atlantic Sturgeon (USGS)**:\\n - The USGS is conducting research on the conservation genetics of Atlantic sturgeon, with a focus on genetic assignment testing and population genetic studies. This is in response to the rediscovery of populations that were previously thought to be extirpated, necessitating updated management strategies. [Learn more from USGS](https://www.usgs.gov/centers/eesc/science/conservation-genetics-atlantic-sturgeon).\\n\\n5. **Restoration Efforts Along the Ontonagon River and St. Louis River**:\\n - The Iron River National Fish Hatchery and Ashland Fish and Wildlife Conservation Office are working with partners to restore lake sturgeon in Michigan and Minnesota. Efforts include collecting larval sturgeon and milt for breeding and stocking programs. [Read the story](https://www.fws.gov/story/2024-04/restoring-reverence-along-lake-sturgeon).\\n\\nThese resources provide comprehensive data on the state of North American sturgeon species and the various conservation efforts being undertaken to preserve and enhance their populations. If you require further information or assistance, feel free to ask.')]}}\n",
|
||||
"---\n",
|
||||
"{'supervisor': {'next': 'ResearchTeam'}}\n",
|
||||
"---\n",
|
||||
"{'ResearchTeam': {'messages': [HumanMessage(content='Based on the search results obtained, here is a summary of the information available regarding the conservation status, size, lifespan, and conservation efforts for North American sturgeon species:\\n\\n1. **Reconnecting Fragmented Sturgeon Populations in North American Rivers (Jager et al.)**:\\n - This paper discusses the fragmentation of large North American rivers by dams that interrupt the migrations of wide-ranging fishes like sturgeons. Efforts to reconnect habitats are viewed as crucial for protecting sturgeon species in U.S. rivers, as these species have lost between 5% and 60% of their historical ranges. [Learn more from the paper](https://www.semanticscholar.org/paper/Reconnecting-Fragmented-Sturgeon-Populations-in-Jager-Parsley/45414d7c86cd2d4f04b9490c7143f36b5158e729/figure/0).\\n\\n2. **North American Sturgeon and Paddlefish Society (NASPS)**:\\n - NASPS is dedicated to promoting the conservation and restoration of sturgeon species in North America by developing and advancing research pertaining to their biology, management, and utilization. [Visit NASPS](https://nasps-sturgeon.org/about/).\\n\\n3. **U.S. Fish and Wildlife Service on Lake Sturgeon and the Endangered Species Act**:\\n - The U.S. Fish and Wildlife Service determined that lake sturgeon do not require listing under the Endangered Species Act, thanks to ongoing management efforts such as fish stocking that have contributed to their population stability. [Read the press release](https://www.fws.gov/press-release/2024-04/collaborative-conservation-keeps-lake-sturgeon-endangered-list).\\n\\n4. **Conservation Genetics of Atlantic Sturgeon (USGS)**:\\n - The USGS is conducting research on the conservation genetics of Atlantic sturgeon, with a focus on genetic assignment testing and population genetic studies. This is in response to the rediscovery of populations that were previously thought to be extirpated, necessitating updated management strategies. [Learn more from USGS](https://www.usgs.gov/centers/eesc/science/conservation-genetics-atlantic-sturgeon).\\n\\n5. **Restoration Efforts Along the Ontonagon River and St. Louis River**:\\n - The Iron River National Fish Hatchery and Ashland Fish and Wildlife Conservation Office are working with partners to restore lake sturgeon in Michigan and Minnesota. Efforts include collecting larval sturgeon and milt for breeding and stocking programs. [Read the story](https://www.fws.gov/story/2024-04/restoring-reverence-along-lake-sturgeon).\\n\\nThese resources provide comprehensive data on the state of North American sturgeon species and the various conservation efforts being undertaken to preserve and enhance their populations. If you require further information or assistance, feel free to ask.')]}}\n",
|
||||
"---\n",
|
||||
"{'supervisor': {'next': 'ResearchTeam'}}\n",
|
||||
"---\n",
|
||||
"{'ResearchTeam': {'messages': [HumanMessage(content='Based on the search results obtained, here is a summary of the information available regarding the conservation status, size, lifespan, and conservation efforts for North American sturgeon species:\\n\\n1. **Reconnecting Fragmented Sturgeon Populations in North American Rivers (Jager et al.)**:\\n - This paper discusses the fragmentation of large North American rivers by dams that interrupt the migrations of wide-ranging fishes like sturgeons. Efforts to reconnect habitats are viewed as crucial for protecting sturgeon species in U.S. rivers, as these species have lost between 5% and 60% of their historical ranges. [Learn more from the paper](https://www.semanticscholar.org/paper/Reconnecting-Fragmented-Sturgeon-Populations-in-Jager-Parsley/45414d7c86cd2d4f04b9490c7143f36b5158e729/figure/0).\\n\\n2. **North American Sturgeon and Paddlefish Society (NASPS)**:\\n - NASPS is dedicated to promoting the conservation and restoration of sturgeon species in North America by developing and advancing research pertaining to their biology, management, and utilization. [Visit NASPS](https://nasps-sturgeon.org/about/).\\n\\n3. **U.S. Fish and Wildlife Service on Lake Sturgeon and the Endangered Species Act**:\\n - The U.S. Fish and Wildlife Service determined that lake sturgeon do not require listing under the Endangered Species Act, thanks to ongoing management efforts such as fish stocking that have contributed to their population stability. [Read the press release](https://www.fws.gov/press-release/2024-04/collaborative-conservation-keeps-lake-sturgeon-endangered-list).\\n\\n4. **Conservation Genetics of Atlantic Sturgeon (USGS)**:\\n - The USGS is conducting research on the conservation genetics of Atlantic sturgeon, with a focus on genetic assignment testing and population genetic studies. This is in response to the rediscovery of populations that were previously thought to be extirpated, necessitating updated management strategies. [Learn more from USGS](https://www.usgs.gov/centers/eesc/science/conservation-genetics-atlantic-sturgeon).\\n\\n5. **Restoration Efforts Along the Ontonagon River and St. Louis River**:\\n - The Iron River National Fish Hatchery and Ashland Fish and Wildlife Conservation Office are working with partners to restore lake sturgeon in Michigan and Minnesota. Efforts include collecting larval sturgeon and milt for breeding and stocking programs. [Read the story](https://www.fws.gov/story/2024-04/restoring-reverence-along-lake-sturgeon).\\n\\nThese resources provide comprehensive data on the state of North American sturgeon species and the various conservation efforts being undertaken to preserve and enhance their populations. If you require further information or assistance, feel free to ask.')]}}\n",
|
||||
"---\n",
|
||||
"{'supervisor': {'next': 'ResearchTeam'}}\n",
|
||||
"---\n",
|
||||
"{'ResearchTeam': {'messages': [HumanMessage(content='Based on the search results obtained, here is a summary of the information available regarding the conservation status, size, lifespan, and conservation efforts for North American sturgeon species:\\n\\n1. **Reconnecting Fragmented Sturgeon Populations in North American Rivers (Jager et al.)**:\\n - This paper discusses the fragmentation of large North American rivers by dams that interrupt the migrations of wide-ranging fishes like sturgeons. Efforts to reconnect habitats are viewed as crucial for protecting sturgeon species in U.S. rivers, as these species have lost between 5% and 60% of their historical ranges. [Learn more from the paper](https://www.semanticscholar.org/paper/Reconnecting-Fragmented-Sturgeon-Populations-in-Jager-Parsley/45414d7c86cd2d4f04b9490c7143f36b5158e729/figure/0).\\n\\n2. **North American Sturgeon and Paddlefish Society (NASPS)**:\\n - NASPS is dedicated to promoting the conservation and restoration of sturgeon species in North America by developing and advancing research pertaining to their biology, management, and utilization. [Visit NASPS](https://nasps-sturgeon.org/about/).\\n\\n3. **U.S. Fish and Wildlife Service on Lake Sturgeon and the Endangered Species Act**:\\n - The U.S. Fish and Wildlife Service determined that lake sturgeon do not require listing under the Endangered Species Act, thanks to ongoing management efforts such as fish stocking that have contributed to their population stability. [Read the press release](https://www.fws.gov/press-release/2024-04/collaborative-conservation-keeps-lake-sturgeon-endangered-list).\\n\\n4. **Conservation Genetics of Atlantic Sturgeon (USGS)**:\\n - The USGS is conducting research on the conservation genetics of Atlantic sturgeon, with a focus on genetic assignment testing and population genetic studies. This is in response to the rediscovery of populations that were previously thought to be extirpated, necessitating updated management strategies. [Learn more from USGS](https://www.usgs.gov/centers/eesc/science/conservation-genetics-atlantic-sturgeon).\\n\\n5. **Restoration Efforts Along the Ontonagon River and St. Louis River**:\\n - The Iron River National Fish Hatchery and Ashland Fish and Wildlife Conservation Office are working with partners to restore lake sturgeon in Michigan and Minnesota. Efforts include collecting larval sturgeon and milt for breeding and stocking programs. [Read the story](https://www.fws.gov/story/2024-04/restoring-reverence-along-lake-sturgeon).\\n\\nThese resources provide comprehensive data on the state of North American sturgeon species and the various conservation efforts being undertaken to preserve and enhance their populations. If you require further information or assistance, feel free to ask.')]}}\n",
|
||||
"---\n",
|
||||
"{'supervisor': {'next': 'ResearchTeam'}}\n",
|
||||
"---\n",
|
||||
"{'ResearchTeam': {'messages': [HumanMessage(content='Based on the search results obtained, here is a summary of the information available regarding the conservation status, size, lifespan, and conservation efforts for North American sturgeon species:\\n\\n1. **Reconnecting Fragmented Sturgeon Populations in North American Rivers (Jager et al.)**:\\n - This paper discusses the fragmentation of large North American rivers by dams that interrupt the migrations of wide-ranging fishes like sturgeons. Efforts to reconnect habitats are viewed as crucial for protecting sturgeon species in U.S. rivers, as these species have lost between 5% and 60% of their historical ranges. [Learn more from the paper](https://www.semanticscholar.org/paper/Reconnecting-Fragmented-Sturgeon-Populations-in-Jager-Parsley/45414d7c86cd2d4f04b9490c7143f36b5158e729/figure/0).\\n\\n2. **North American Sturgeon and Paddlefish Society (NASPS)**:\\n - NASPS is dedicated to promoting the conservation and restoration of sturgeon species in North America by developing and advancing research pertaining to their biology, management, and utilization. [Visit NASPS](https://nasps-sturgeon.org/about/).\\n\\n3. **U.S. Fish and Wildlife Service on Lake Sturgeon and the Endangered Species Act**:\\n - The U.S. Fish and Wildlife Service determined that lake sturgeon do not require listing under the Endangered Species Act, thanks to ongoing management efforts such as fish stocking that have contributed to their population stability. [Read the press release](https://www.fws.gov/press-release/2024-04/collaborative-conservation-keeps-lake-sturgeon-endangered-list).\\n\\n4. **Conservation Genetics of Atlantic Sturgeon (USGS)**:\\n - The USGS is conducting research on the conservation genetics of Atlantic sturgeon, with a focus on genetic assignment testing and population genetic studies. This is in response to the rediscovery of populations that were previously thought to be extirpated, necessitating updated management strategies. [Learn more from USGS](https://www.usgs.gov/centers/eesc/science/conservation-genetics-atlantic-sturgeon).\\n\\n5. **Restoration Efforts Along the Ontonagon River and St. Louis River**:\\n - The Iron River National Fish Hatchery and Ashland Fish and Wildlife Conservation Office are working with partners to restore lake sturgeon in Michigan and Minnesota. Efforts include collecting larval sturgeon and milt for breeding and stocking programs. [Read the story](https://www.fws.gov/story/2024-04/restoring-reverence-along-lake-sturgeon).\\n\\nThese resources provide comprehensive data on the state of North American sturgeon species and the various conservation efforts being undertaken to preserve and enhance their populations. If you require further information or assistance, feel free to ask.')]}}\n",
|
||||
"---\n",
|
||||
"{'supervisor': {'next': 'ResearchTeam'}}\n",
|
||||
"---\n",
|
||||
"{'ResearchTeam': {'messages': [HumanMessage(content='Based on the search results obtained, here is a summary of the information available regarding the conservation status, size, lifespan, and conservation efforts for North American sturgeon species:\\n\\n1. **Reconnecting Fragmented Sturgeon Populations in North American Rivers (Jager et al.)**:\\n - This paper discusses the fragmentation of large North American rivers by dams that interrupt the migrations of wide-ranging fishes like sturgeons. Efforts to reconnect habitats are viewed as crucial for protecting sturgeon species in U.S. rivers, as these species have lost between 5% and 60% of their historical ranges. [Learn more from the paper](https://www.semanticscholar.org/paper/Reconnecting-Fragmented-Sturgeon-Populations-in-Jager-Parsley/45414d7c86cd2d4f04b9490c7143f36b5158e729/figure/0).\\n\\n2. **North American Sturgeon and Paddlefish Society (NASPS)**:\\n - NASPS is dedicated to promoting the conservation and restoration of sturgeon species in North America by developing and advancing research pertaining to their biology, management, and utilization. [Visit NASPS](https://nasps-sturgeon.org/about/).\\n\\n3. **U.S. Fish and Wildlife Service on Lake Sturgeon and the Endangered Species Act**:\\n - The U.S. Fish and Wildlife Service determined that lake sturgeon do not require listing under the Endangered Species Act, thanks to ongoing management efforts such as fish stocking that have contributed to their population stability. [Read the press release](https://www.fws.gov/press-release/2024-04/collaborative-conservation-keeps-lake-sturgeon-endangered-list).\\n\\n4. **Conservation Genetics of Atlantic Sturgeon (USGS)**:\\n - The USGS is conducting research on the conservation genetics of Atlantic sturgeon, with a focus on genetic assignment testing and population genetic studies. This is in response to the rediscovery of populations that were previously thought to be extirpated, necessitating updated management strategies. [Learn more from USGS](https://www.usgs.gov/centers/eesc/science/conservation-genetics-atlantic-sturgeon).\\n\\n5. **Restoration Efforts Along the Ontonagon River and St. Louis River**:\\n - The Iron River National Fish Hatchery and Ashland Fish and Wildlife Conservation Office are working with partners to restore lake sturgeon in Michigan and Minnesota. Efforts include collecting larval sturgeon and milt for breeding and stocking programs. [Read the story](https://www.fws.gov/story/2024-04/restoring-reverence-along-lake-sturgeon).\\n\\nThese resources provide comprehensive data on the state of North American sturgeon species and the various conservation efforts being undertaken to preserve and enhance their populations. If you require further information or assistance, feel free to ask.')]}}\n",
|
||||
"---\n",
|
||||
"{'supervisor': {'next': 'ResearchTeam'}}\n",
|
||||
"---\n",
|
||||
"{'ResearchTeam': {'messages': [HumanMessage(content='Based on the search results obtained, here is a summary of the information available regarding the conservation status, size, lifespan, and conservation efforts for North American sturgeon species:\\n\\n1. **Reconnecting Fragmented Sturgeon Populations in North American Rivers (Jager et al.)**:\\n - This paper discusses the fragmentation of large North American rivers by dams that interrupt the migrations of wide-ranging fishes like sturgeons. Efforts to reconnect habitats are viewed as crucial for protecting sturgeon species in U.S. rivers, as these species have lost between 5% and 60% of their historical ranges. [Learn more from the paper](https://www.semanticscholar.org/paper/Reconnecting-Fragmented-Sturgeon-Populations-in-Jager-Parsley/45414d7c86cd2d4f04b9490c7143f36b5158e729/figure/0).\\n\\n2. **North American Sturgeon and Paddlefish Society (NASPS)**:\\n - NASPS is dedicated to promoting the conservation and restoration of sturgeon species in North America by developing and advancing research pertaining to their biology, management, and utilization. [Visit NASPS](https://nasps-sturgeon.org/about/).\\n\\n3. **U.S. Fish and Wildlife Service on Lake Sturgeon and the Endangered Species Act**:\\n - The U.S. Fish and Wildlife Service determined that lake sturgeon do not require listing under the Endangered Species Act, thanks to ongoing management efforts such as fish stocking that have contributed to their population stability. [Read the press release](https://www.fws.gov/press-release/2024-04/collaborative-conservation-keeps-lake-sturgeon-endangered-list).\\n\\n4. **Conservation Genetics of Atlantic Sturgeon (USGS)**:\\n - The USGS is conducting research on the conservation genetics of Atlantic sturgeon, with a focus on genetic assignment testing and population genetic studies. This is in response to the rediscovery of populations that were previously thought to be extirpated, necessitating updated management strategies. [Learn more from USGS](https://www.usgs.gov/centers/eesc/science/conservation-genetics-atlantic-sturgeon).\\n\\n5. **Restoration Efforts Along the Ontonagon River and St. Louis River**:\\n - The Iron River National Fish Hatchery and Ashland Fish and Wildlife Conservation Office are working with partners to restore lake sturgeon in Michigan and Minnesota. Efforts include collecting larval sturgeon and milt for breeding and stocking programs. [Read the story](https://www.fws.gov/story/2024-04/restoring-reverence-along-lake-sturgeon).\\n\\nThese resources provide comprehensive data on the state of North American sturgeon species and the various conservation efforts being undertaken to preserve and enhance their populations. If you require further information or assistance, feel free to ask.')]}}\n",
|
||||
"---\n",
|
||||
"{'supervisor': {'next': 'ResearchTeam'}}\n",
|
||||
"---\n",
|
||||
"{'ResearchTeam': {'messages': [HumanMessage(content='Based on the search results obtained, here is a summary of the information available regarding the conservation status, size, lifespan, and conservation efforts for North American sturgeon species:\\n\\n1. **Reconnecting Fragmented Sturgeon Populations in North American Rivers (Jager et al.)**:\\n - This paper discusses the fragmentation of large North American rivers by dams that interrupt the migrations of wide-ranging fishes like sturgeons. Efforts to reconnect habitats are viewed as crucial for protecting sturgeon species in U.S. rivers, as these species have lost between 5% and 60% of their historical ranges. [Learn more from the paper](https://www.semanticscholar.org/paper/Reconnecting-Fragmented-Sturgeon-Populations-in-Jager-Parsley/45414d7c86cd2d4f04b9490c7143f36b5158e729/figure/0).\\n\\n2. **North American Sturgeon and Paddlefish Society (NASPS)**:\\n - NASPS is dedicated to promoting the conservation and restoration of sturgeon species in North America by developing and advancing research pertaining to their biology, management, and utilization. [Visit NASPS](https://nasps-sturgeon.org/about/).\\n\\n3. **U.S. Fish and Wildlife Service on Lake Sturgeon and the Endangered Species Act**:\\n - The U.S. Fish and Wildlife Service determined that lake sturgeon do not require listing under the Endangered Species Act, thanks to ongoing management efforts such as fish stocking that have contributed to their population stability. [Read the press release](https://www.fws.gov/press-release/2024-04/collaborative-conservation-keeps-lake-sturgeon-endangered-list).\\n\\n4. **Conservation Genetics of Atlantic Sturgeon (USGS)**:\\n - The USGS is conducting research on the conservation genetics of Atlantic sturgeon, with a focus on genetic assignment testing and population genetic studies. This is in response to the rediscovery of populations that were previously thought to be extirpated, necessitating updated management strategies. [Learn more from USGS](https://www.usgs.gov/centers/eesc/science/conservation-genetics-atlantic-sturgeon).\\n\\n5. **Restoration Efforts Along the Ontonagon River and St. Louis River**:\\n - The Iron River National Fish Hatchery and Ashland Fish and Wildlife Conservation Office are working with partners to restore lake sturgeon in Michigan and Minnesota. Efforts include collecting larval sturgeon and milt for breeding and stocking programs. [Read the story](https://www.fws.gov/story/2024-04/restoring-reverence-along-lake-sturgeon).\\n\\nThese resources provide comprehensive data on the state of North American sturgeon species and the various conservation efforts being undertaken to preserve and enhance their populations. If you require further information or assistance, feel free to ask.')]}}\n",
|
||||
"---\n",
|
||||
"{'supervisor': {'next': 'ResearchTeam'}}\n",
|
||||
"---\n",
|
||||
"{'ResearchTeam': {'messages': [HumanMessage(content='Based on the search results obtained, here is a summary of the information available regarding the conservation status, size, lifespan, and conservation efforts for North American sturgeon species:\\n\\n1. **Reconnecting Fragmented Sturgeon Populations in North American Rivers (Jager et al.)**:\\n - This paper discusses the fragmentation of large North American rivers by dams that interrupt the migrations of wide-ranging fishes like sturgeons. Efforts to reconnect habitats are viewed as crucial for protecting sturgeon species in U.S. rivers, as these species have lost between 5% and 60% of their historical ranges. [Learn more from the paper](https://www.semanticscholar.org/paper/Reconnecting-Fragmented-Sturgeon-Populations-in-Jager-Parsley/45414d7c86cd2d4f04b9490c7143f36b5158e729/figure/0).\\n\\n2. **North American Sturgeon and Paddlefish Society (NASPS)**:\\n - NASPS is dedicated to promoting the conservation and restoration of sturgeon species in North America by developing and advancing research pertaining to their biology, management, and utilization. [Visit NASPS](https://nasps-sturgeon.org/about/).\\n\\n3. **U.S. Fish and Wildlife Service on Lake Sturgeon and the Endangered Species Act**:\\n - The U.S. Fish and Wildlife Service determined that lake sturgeon do not require listing under the Endangered Species Act, thanks to ongoing management efforts such as fish stocking that have contributed to their population stability. [Read the press release](https://www.fws.gov/press-release/2024-04/collaborative-conservation-keeps-lake-sturgeon-endangered-list).\\n\\n4. **Conservation Genetics of Atlantic Sturgeon (USGS)**:\\n - The USGS is conducting research on the conservation genetics of Atlantic sturgeon, with a focus on genetic assignment testing and population genetic studies. This is in response to the rediscovery of populations that were previously thought to be extirpated, necessitating updated management strategies. [Learn more from USGS](https://www.usgs.gov/centers/eesc/science/conservation-genetics-atlantic-sturgeon).\\n\\n5. **Restoration Efforts Along the Ontonagon River and St. Louis River**:\\n - The Iron River National Fish Hatchery and Ashland Fish and Wildlife Conservation Office are working with partners to restore lake sturgeon in Michigan and Minnesota. Efforts include collecting larval sturgeon and milt for breeding and stocking programs. [Read the story](https://www.fws.gov/story/2024-04/restoring-reverence-along-lake-sturgeon).\\n\\nThese resources provide comprehensive data on the state of North American sturgeon species and the various conservation efforts being undertaken to preserve and enhance their populations. If you require further information or assistance, feel free to ask.')]}}\n",
|
||||
"---\n",
|
||||
"{'supervisor': {'next': 'ResearchTeam'}}\n",
|
||||
"---\n",
|
||||
"{'ResearchTeam': {'messages': [HumanMessage(content='Based on the search results obtained, here is a summary of the information available regarding the conservation status, size, lifespan, and conservation efforts for North American sturgeon species:\\n\\n1. **Reconnecting Fragmented Sturgeon Populations in North American Rivers (Jager et al.)**:\\n - This paper discusses the fragmentation of large North American rivers by dams that interrupt the migrations of wide-ranging fishes like sturgeons. Efforts to reconnect habitats are viewed as crucial for protecting sturgeon species in U.S. rivers, as these species have lost between 5% and 60% of their historical ranges. [Learn more from the paper](https://www.semanticscholar.org/paper/Reconnecting-Fragmented-Sturgeon-Populations-in-Jager-Parsley/45414d7c86cd2d4f04b9490c7143f36b5158e729/figure/0).\\n\\n2. **North American Sturgeon and Paddlefish Society (NASPS)**:\\n - NASPS is dedicated to promoting the conservation and restoration of sturgeon species in North America by developing and advancing research pertaining to their biology, management, and utilization. [Visit NASPS](https://nasps-sturgeon.org/about/).\\n\\n3. **U.S. Fish and Wildlife Service on Lake Sturgeon and the Endangered Species Act**:\\n - The U.S. Fish and Wildlife Service determined that lake sturgeon do not require listing under the Endangered Species Act, thanks to ongoing management efforts such as fish stocking that have contributed to their population stability. [Read the press release](https://www.fws.gov/press-release/2024-04/collaborative-conservation-keeps-lake-sturgeon-endangered-list).\\n\\n4. **Conservation Genetics of Atlantic Sturgeon (USGS)**:\\n - The USGS is conducting research on the conservation genetics of Atlantic sturgeon, with a focus on genetic assignment testing and population genetic studies. This is in response to the rediscovery of populations that were previously thought to be extirpated, necessitating updated management strategies. [Learn more from USGS](https://www.usgs.gov/centers/eesc/science/conservation-genetics-atlantic-sturgeon).\\n\\n5. **Restoration Efforts Along the Ontonagon River and St. Louis River**:\\n - The Iron River National Fish Hatchery and Ashland Fish and Wildlife Conservation Office are working with partners to restore lake sturgeon in Michigan and Minnesota. Efforts include collecting larval sturgeon and milt for breeding and stocking programs. [Read the story](https://www.fws.gov/story/2024-04/restoring-reverence-along-lake-sturgeon).\\n\\nThese resources provide comprehensive data on the state of North American sturgeon species and the various conservation efforts being undertaken to preserve and enhance their populations. If you require further information or assistance, feel free to ask.')]}}\n",
|
||||
"---\n",
|
||||
"{'supervisor': {'next': 'ResearchTeam'}}\n",
|
||||
"---\n",
|
||||
"{'ResearchTeam': {'messages': [HumanMessage(content='Based on the search results obtained, here is a summary of the information available regarding the conservation status, size, lifespan, and conservation efforts for North American sturgeon species:\\n\\n1. **Reconnecting Fragmented Sturgeon Populations in North American Rivers (Jager et al.)**:\\n - This paper discusses the fragmentation of large North American rivers by dams that interrupt the migrations of wide-ranging fishes like sturgeons. Efforts to reconnect habitats are viewed as crucial for protecting sturgeon species in U.S. rivers, as these species have lost between 5% and 60% of their historical ranges. [Learn more from the paper](https://www.semanticscholar.org/paper/Reconnecting-Fragmented-Sturgeon-Populations-in-Jager-Parsley/45414d7c86cd2d4f04b9490c7143f36b5158e729/figure/0).\\n\\n2. **North American Sturgeon and Paddlefish Society (NASPS)**:\\n - NASPS is dedicated to promoting the conservation and restoration of sturgeon species in North America by developing and advancing research pertaining to their biology, management, and utilization. [Visit NASPS](https://nasps-sturgeon.org/about/).\\n\\n3. **U.S. Fish and Wildlife Service on Lake Sturgeon and the Endangered Species Act**:\\n - The U.S. Fish and Wildlife Service determined that lake sturgeon do not require listing under the Endangered Species Act, thanks to ongoing management efforts such as fish stocking that have contributed to their population stability. [Read the press release](https://www.fws.gov/press-release/2024-04/collaborative-conservation-keeps-lake-sturgeon-endangered-list).\\n\\n4. **Conservation Genetics of Atlantic Sturgeon (USGS)**:\\n - The USGS is conducting research on the conservation genetics of Atlantic sturgeon, with a focus on genetic assignment testing and population genetic studies. This is in response to the rediscovery of populations that were previously thought to be extirpated, necessitating updated management strategies. [Learn more from USGS](https://www.usgs.gov/centers/eesc/science/conservation-genetics-atlantic-sturgeon).\\n\\n5. **Restoration Efforts Along the Ontonagon River and St. Louis River**:\\n - The Iron River National Fish Hatchery and Ashland Fish and Wildlife Conservation Office are working with partners to restore lake sturgeon in Michigan and Minnesota. Efforts include collecting larval sturgeon and milt for breeding and stocking programs. [Read the story](https://www.fws.gov/story/2024-04/restoring-reverence-along-lake-sturgeon).\\n\\nThese resources provide comprehensive data on the state of North American sturgeon species and the various conservation efforts being undertaken to preserve and enhance their populations. If you require further information or assistance, feel free to ask.')]}}\n",
|
||||
"---\n",
|
||||
"{'supervisor': {'next': 'ResearchTeam'}}\n",
|
||||
"---\n",
|
||||
"{'ResearchTeam': {'messages': [HumanMessage(content='Based on the search results obtained, here is a summary of the information available regarding the conservation status, size, lifespan, and conservation efforts for North American sturgeon species:\\n\\n1. **Reconnecting Fragmented Sturgeon Populations in North American Rivers (Jager et al.)**:\\n - This paper discusses the fragmentation of large North American rivers by dams that interrupt the migrations of wide-ranging fishes like sturgeons. Efforts to reconnect habitats are viewed as crucial for protecting sturgeon species in U.S. rivers, as these species have lost between 5% and 60% of their historical ranges. [Learn more from the paper](https://www.semanticscholar.org/paper/Reconnecting-Fragmented-Sturgeon-Populations-in-Jager-Parsley/45414d7c86cd2d4f04b9490c7143f36b5158e729/figure/0).\\n\\n2. **North American Sturgeon and Paddlefish Society (NASPS)**:\\n - NASPS is dedicated to promoting the conservation and restoration of sturgeon species in North America by developing and advancing research pertaining to their biology, management, and utilization. [Visit NASPS](https://nasps-sturgeon.org/about/).\\n\\n3. **U.S. Fish and Wildlife Service on Lake Sturgeon and the Endangered Species Act**:\\n - The U.S. Fish and Wildlife Service determined that lake sturgeon do not require listing under the Endangered Species Act, thanks to ongoing management efforts such as fish stocking that have contributed to their population stability. [Read the press release](https://www.fws.gov/press-release/2024-04/collaborative-conservation-keeps-lake-sturgeon-endangered-list).\\n\\n4. **Conservation Genetics of Atlantic Sturgeon (USGS)**:\\n - The USGS is conducting research on the conservation genetics of Atlantic sturgeon, with a focus on genetic assignment testing and population genetic studies. This is in response to the rediscovery of populations that were previously thought to be extirpated, necessitating updated management strategies. [Learn more from USGS](https://www.usgs.gov/centers/eesc/science/conservation-genetics-atlantic-sturgeon).\\n\\n5. **Restoration Efforts Along the Ontonagon River and St. Louis River**:\\n - The Iron River National Fish Hatchery and Ashland Fish and Wildlife Conservation Office are working with partners to restore lake sturgeon in Michigan and Minnesota. Efforts include collecting larval sturgeon and milt for breeding and stocking programs. [Read the story](https://www.fws.gov/story/2024-04/restoring-reverence-along-lake-sturgeon).\\n\\nThese resources provide comprehensive data on the state of North American sturgeon species and the various conservation efforts being undertaken to preserve and enhance their populations. If you require further information or assistance, feel free to ask.')]}}\n",
|
||||
"---\n",
|
||||
"{'supervisor': {'next': 'ResearchTeam'}}\n",
|
||||
"---\n",
|
||||
"{'ResearchTeam': {'messages': [HumanMessage(content='Based on the search results obtained, here is a summary of the information available regarding the conservation status, size, lifespan, and conservation efforts for North American sturgeon species:\\n\\n1. **Reconnecting Fragmented Sturgeon Populations in North American Rivers (Jager et al.)**:\\n - This paper discusses the fragmentation of large North American rivers by dams that interrupt the migrations of wide-ranging fishes like sturgeons. Efforts to reconnect habitats are viewed as crucial for protecting sturgeon species in U.S. rivers, as these species have lost between 5% and 60% of their historical ranges. [Learn more from the paper](https://www.semanticscholar.org/paper/Reconnecting-Fragmented-Sturgeon-Populations-in-Jager-Parsley/45414d7c86cd2d4f04b9490c7143f36b5158e729/figure/0).\\n\\n2. **North American Sturgeon and Paddlefish Society (NASPS)**:\\n - NASPS is dedicated to promoting the conservation and restoration of sturgeon species in North America by developing and advancing research pertaining to their biology, management, and utilization. [Visit NASPS](https://nasps-sturgeon.org/about/).\\n\\n3. **U.S. Fish and Wildlife Service on Lake Sturgeon and the Endangered Species Act**:\\n - The U.S. Fish and Wildlife Service determined that lake sturgeon do not require listing under the Endangered Species Act, thanks to ongoing management efforts such as fish stocking that have contributed to their population stability. [Read the press release](https://www.fws.gov/press-release/2024-04/collaborative-conservation-keeps-lake-sturgeon-endangered-list).\\n\\n4. **Conservation Genetics of Atlantic Sturgeon (USGS)**:\\n - The USGS is conducting research on the conservation genetics of Atlantic sturgeon, with a focus on genetic assignment testing and population genetic studies. This is in response to the rediscovery of populations that were previously thought to be extirpated, necessitating updated management strategies. [Learn more from USGS](https://www.usgs.gov/centers/eesc/science/conservation-genetics-atlantic-sturgeon).\\n\\n5. **Restoration Efforts Along the Ontonagon River and St. Louis River**:\\n - The Iron River National Fish Hatchery and Ashland Fish and Wildlife Conservation Office are working with partners to restore lake sturgeon in Michigan and Minnesota. Efforts include collecting larval sturgeon and milt for breeding and stocking programs. [Read the story](https://www.fws.gov/story/2024-04/restoring-reverence-along-lake-sturgeon).\\n\\nThese resources provide comprehensive data on the state of North American sturgeon species and the various conservation efforts being undertaken to preserve and enhance their populations. If you require further information or assistance, feel free to ask.')]}}\n",
|
||||
"---\n",
|
||||
"{'supervisor': {'next': 'ResearchTeam'}}\n",
|
||||
"---\n",
|
||||
"{'ResearchTeam': {'messages': [HumanMessage(content='Based on the search results obtained, here is a summary of the information available regarding the conservation status, size, lifespan, and conservation efforts for North American sturgeon species:\\n\\n1. **Reconnecting Fragmented Sturgeon Populations in North American Rivers (Jager et al.)**:\\n - This paper discusses the fragmentation of large North American rivers by dams that interrupt the migrations of wide-ranging fishes like sturgeons. Efforts to reconnect habitats are viewed as crucial for protecting sturgeon species in U.S. rivers, as these species have lost between 5% and 60% of their historical ranges. [Learn more from the paper](https://www.semanticscholar.org/paper/Reconnecting-Fragmented-Sturgeon-Populations-in-Jager-Parsley/45414d7c86cd2d4f04b9490c7143f36b5158e729/figure/0).\\n\\n2. **North American Sturgeon and Paddlefish Society (NASPS)**:\\n - NASPS is dedicated to promoting the conservation and restoration of sturgeon species in North America by developing and advancing research pertaining to their biology, management, and utilization. [Visit NASPS](https://nasps-sturgeon.org/about/).\\n\\n3. **U.S. Fish and Wildlife Service on Lake Sturgeon and the Endangered Species Act**:\\n - The U.S. Fish and Wildlife Service determined that lake sturgeon do not require listing under the Endangered Species Act, thanks to ongoing management efforts such as fish stocking that have contributed to their population stability. [Read the press release](https://www.fws.gov/press-release/2024-04/collaborative-conservation-keeps-lake-sturgeon-endangered-list).\\n\\n4. **Conservation Genetics of Atlantic Sturgeon (USGS)**:\\n - The USGS is conducting research on the conservation genetics of Atlantic sturgeon, with a focus on genetic assignment testing and population genetic studies. This is in response to the rediscovery of populations that were previously thought to be extirpated, necessitating updated management strategies. [Learn more from USGS](https://www.usgs.gov/centers/eesc/science/conservation-genetics-atlantic-sturgeon).\\n\\n5. **Restoration Efforts Along the Ontonagon River and St. Louis River**:\\n - The Iron River National Fish Hatchery and Ashland Fish and Wildlife Conservation Office are working with partners to restore lake sturgeon in Michigan and Minnesota. Efforts include collecting larval sturgeon and milt for breeding and stocking programs. [Read the story](https://www.fws.gov/story/2024-04/restoring-reverence-along-lake-sturgeon).\\n\\nThese resources provide comprehensive data on the state of North American sturgeon species and the various conservation efforts being undertaken to preserve and enhance their populations. If you require further information or assistance, feel free to ask.')]}}\n",
|
||||
"---\n",
|
||||
"{'supervisor': {'next': 'ResearchTeam'}}\n",
|
||||
"---\n",
|
||||
"{'ResearchTeam': {'messages': [HumanMessage(content='Based on the search results obtained, here is a summary of the information available regarding the conservation status, size, lifespan, and conservation efforts for North American sturgeon species:\\n\\n1. **Reconnecting Fragmented Sturgeon Populations in North American Rivers (Jager et al.)**:\\n - This paper discusses the fragmentation of large North American rivers by dams that interrupt the migrations of wide-ranging fishes like sturgeons. Efforts to reconnect habitats are viewed as crucial for protecting sturgeon species in U.S. rivers, as these species have lost between 5% and 60% of their historical ranges. [Learn more from the paper](https://www.semanticscholar.org/paper/Reconnecting-Fragmented-Sturgeon-Populations-in-Jager-Parsley/45414d7c86cd2d4f04b9490c7143f36b5158e729/figure/0).\\n\\n2. **North American Sturgeon and Paddlefish Society (NASPS)**:\\n - NASPS is dedicated to promoting the conservation and restoration of sturgeon species in North America by developing and advancing research pertaining to their biology, management, and utilization. [Visit NASPS](https://nasps-sturgeon.org/about/).\\n\\n3. **U.S. Fish and Wildlife Service on Lake Sturgeon and the Endangered Species Act**:\\n - The U.S. Fish and Wildlife Service determined that lake sturgeon do not require listing under the Endangered Species Act, thanks to ongoing management efforts such as fish stocking that have contributed to their population stability. [Read the press release](https://www.fws.gov/press-release/2024-04/collaborative-conservation-keeps-lake-sturgeon-endangered-list).\\n\\n4. **Conservation Genetics of Atlantic Sturgeon (USGS)**:\\n - The USGS is conducting research on the conservation genetics of Atlantic sturgeon, with a focus on genetic assignment testing and population genetic studies. This is in response to the rediscovery of populations that were previously thought to be extirpated, necessitating updated management strategies. [Learn more from USGS](https://www.usgs.gov/centers/eesc/science/conservation-genetics-atlantic-sturgeon).\\n\\n5. **Restoration Efforts Along the Ontonagon River and St. Louis River**:\\n - The Iron River National Fish Hatchery and Ashland Fish and Wildlife Conservation Office are working with partners to restore lake sturgeon in Michigan and Minnesota. Efforts include collecting larval sturgeon and milt for breeding and stocking programs. [Read the story](https://www.fws.gov/story/2024-04/restoring-reverence-along-lake-sturgeon).\\n\\nThese resources provide comprehensive data on the state of North American sturgeon species and the various conservation efforts being undertaken to preserve and enhance their populations. If you require further information or assistance, feel free to ask.')]}}\n",
|
||||
"---\n",
|
||||
"{'supervisor': {'next': 'ResearchTeam'}}\n",
|
||||
"---\n",
|
||||
"{'ResearchTeam': {'messages': [HumanMessage(content='Based on the search results obtained, here is a summary of the information available regarding the conservation status, size, lifespan, and conservation efforts for North American sturgeon species:\\n\\n1. **Reconnecting Fragmented Sturgeon Populations in North American Rivers (Jager et al.)**:\\n - This paper discusses the fragmentation of large North American rivers by dams that interrupt the migrations of wide-ranging fishes like sturgeons. Efforts to reconnect habitats are viewed as crucial for protecting sturgeon species in U.S. rivers, as these species have lost between 5% and 60% of their historical ranges. [Learn more from the paper](https://www.semanticscholar.org/paper/Reconnecting-Fragmented-Sturgeon-Populations-in-Jager-Parsley/45414d7c86cd2d4f04b9490c7143f36b5158e729/figure/0).\\n\\n2. **North American Sturgeon and Paddlefish Society (NASPS)**:\\n - NASPS is dedicated to promoting the conservation and restoration of sturgeon species in North America by developing and advancing research pertaining to their biology, management, and utilization. [Visit NASPS](https://nasps-sturgeon.org/about/).\\n\\n3. **U.S. Fish and Wildlife Service on Lake Sturgeon and the Endangered Species Act**:\\n - The U.S. Fish and Wildlife Service determined that lake sturgeon do not require listing under the Endangered Species Act, thanks to ongoing management efforts such as fish stocking that have contributed to their population stability. [Read the press release](https://www.fws.gov/press-release/2024-04/collaborative-conservation-keeps-lake-sturgeon-endangered-list).\\n\\n4. **Conservation Genetics of Atlantic Sturgeon (USGS)**:\\n - The USGS is conducting research on the conservation genetics of Atlantic sturgeon, with a focus on genetic assignment testing and population genetic studies. This is in response to the rediscovery of populations that were previously thought to be extirpated, necessitating updated management strategies. [Learn more from USGS](https://www.usgs.gov/centers/eesc/science/conservation-genetics-atlantic-sturgeon).\\n\\n5. **Restoration Efforts Along the Ontonagon River and St. Louis River**:\\n - The Iron River National Fish Hatchery and Ashland Fish and Wildlife Conservation Office are working with partners to restore lake sturgeon in Michigan and Minnesota. Efforts include collecting larval sturgeon and milt for breeding and stocking programs. [Read the story](https://www.fws.gov/story/2024-04/restoring-reverence-along-lake-sturgeon).\\n\\nThese resources provide comprehensive data on the state of North American sturgeon species and the various conservation efforts being undertaken to preserve and enhance their populations. If you require further information or assistance, feel free to ask.')]}}\n",
|
||||
"---\n",
|
||||
"{'supervisor': {'next': 'ResearchTeam'}}\n",
|
||||
"---\n",
|
||||
"{'ResearchTeam': {'messages': [HumanMessage(content='Based on the search results obtained, here is a summary of the information available regarding the conservation status, size, lifespan, and conservation efforts for North American sturgeon species:\\n\\n1. **Reconnecting Fragmented Sturgeon Populations in North American Rivers (Jager et al.)**:\\n - This paper discusses the fragmentation of large North American rivers by dams that interrupt the migrations of wide-ranging fishes like sturgeons. Efforts to reconnect habitats are viewed as crucial for protecting sturgeon species in U.S. rivers, as these species have lost between 5% and 60% of their historical ranges. [Learn more from the paper](https://www.semanticscholar.org/paper/Reconnecting-Fragmented-Sturgeon-Populations-in-Jager-Parsley/45414d7c86cd2d4f04b9490c7143f36b5158e729/figure/0).\\n\\n2. **North American Sturgeon and Paddlefish Society (NASPS)**:\\n - NASPS is dedicated to promoting the conservation and restoration of sturgeon species in North America by developing and advancing research pertaining to their biology, management, and utilization. [Visit NASPS](https://nasps-sturgeon.org/about/).\\n\\n3. **U.S. Fish and Wildlife Service on Lake Sturgeon and the Endangered Species Act**:\\n - The U.S. Fish and Wildlife Service determined that lake sturgeon do not require listing under the Endangered Species Act, thanks to ongoing management efforts such as fish stocking that have contributed to their population stability. [Read the press release](https://www.fws.gov/press-release/2024-04/collaborative-conservation-keeps-lake-sturgeon-endangered-list).\\n\\n4. **Conservation Genetics of Atlantic Sturgeon (USGS)**:\\n - The USGS is conducting research on the conservation genetics of Atlantic sturgeon, with a focus on genetic assignment testing and population genetic studies. This is in response to the rediscovery of populations that were previously thought to be extirpated, necessitating updated management strategies. [Learn more from USGS](https://www.usgs.gov/centers/eesc/science/conservation-genetics-atlantic-sturgeon).\\n\\n5. **Restoration Efforts Along the Ontonagon River and St. Louis River**:\\n - The Iron River National Fish Hatchery and Ashland Fish and Wildlife Conservation Office are working with partners to restore lake sturgeon in Michigan and Minnesota. Efforts include collecting larval sturgeon and milt for breeding and stocking programs. [Read the story](https://www.fws.gov/story/2024-04/restoring-reverence-along-lake-sturgeon).\\n\\nThese resources provide comprehensive data on the state of North American sturgeon species and the various conservation efforts being undertaken to preserve and enhance their populations. If you require further information or assistance, feel free to ask.')]}}\n",
|
||||
"---\n",
|
||||
"{'supervisor': {'next': 'ResearchTeam'}}\n",
|
||||
"---\n",
|
||||
"{'ResearchTeam': {'messages': [HumanMessage(content='Based on the search results obtained, here is a summary of the information available regarding the conservation status, size, lifespan, and conservation efforts for North American sturgeon species:\\n\\n1. **Reconnecting Fragmented Sturgeon Populations in North American Rivers (Jager et al.)**:\\n - This paper discusses the fragmentation of large North American rivers by dams that interrupt the migrations of wide-ranging fishes like sturgeons. Efforts to reconnect habitats are viewed as crucial for protecting sturgeon species in U.S. rivers, as these species have lost between 5% and 60% of their historical ranges. [Learn more from the paper](https://www.semanticscholar.org/paper/Reconnecting-Fragmented-Sturgeon-Populations-in-Jager-Parsley/45414d7c86cd2d4f04b9490c7143f36b5158e729/figure/0).\\n\\n2. **North American Sturgeon and Paddlefish Society (NASPS)**:\\n - NASPS is dedicated to promoting the conservation and restoration of sturgeon species in North America by developing and advancing research pertaining to their biology, management, and utilization. [Visit NASPS](https://nasps-sturgeon.org/about/).\\n\\n3. **U.S. Fish and Wildlife Service on Lake Sturgeon and the Endangered Species Act**:\\n - The U.S. Fish and Wildlife Service determined that lake sturgeon do not require listing under the Endangered Species Act, thanks to ongoing management efforts such as fish stocking that have contributed to their population stability. [Read the press release](https://www.fws.gov/press-release/2024-04/collaborative-conservation-keeps-lake-sturgeon-endangered-list).\\n\\n4. **Conservation Genetics of Atlantic Sturgeon (USGS)**:\\n - The USGS is conducting research on the conservation genetics of Atlantic sturgeon, with a focus on genetic assignment testing and population genetic studies. This is in response to the rediscovery of populations that were previously thought to be extirpated, necessitating updated management strategies. [Learn more from USGS](https://www.usgs.gov/centers/eesc/science/conservation-genetics-atlantic-sturgeon).\\n\\n5. **Restoration Efforts Along the Ontonagon River and St. Louis River**:\\n - The Iron River National Fish Hatchery and Ashland Fish and Wildlife Conservation Office are working with partners to restore lake sturgeon in Michigan and Minnesota. Efforts include collecting larval sturgeon and milt for breeding and stocking programs. [Read the story](https://www.fws.gov/story/2024-04/restoring-reverence-along-lake-sturgeon).\\n\\nThese resources provide comprehensive data on the state of North American sturgeon species and the various conservation efforts being undertaken to preserve and enhance their populations. If you require further information or assistance, feel free to ask.')]}}\n",
|
||||
"---\n",
|
||||
"{'supervisor': {'next': 'ResearchTeam'}}\n",
|
||||
"---\n",
|
||||
"{'ResearchTeam': {'messages': [HumanMessage(content='Based on the search results obtained, here is a summary of the information available regarding the conservation status, size, lifespan, and conservation efforts for North American sturgeon species:\\n\\n1. **Reconnecting Fragmented Sturgeon Populations in North American Rivers (Jager et al.)**:\\n - This paper discusses the fragmentation of large North American rivers by dams that interrupt the migrations of wide-ranging fishes like sturgeons. Efforts to reconnect habitats are viewed as crucial for protecting sturgeon species in U.S. rivers, as these species have lost between 5% and 60% of their historical ranges. [Learn more from the paper](https://www.semanticscholar.org/paper/Reconnecting-Fragmented-Sturgeon-Populations-in-Jager-Parsley/45414d7c86cd2d4f04b9490c7143f36b5158e729/figure/0).\\n\\n2. **North American Sturgeon and Paddlefish Society (NASPS)**:\\n - NASPS is dedicated to promoting the conservation and restoration of sturgeon species in North America by developing and advancing research pertaining to their biology, management, and utilization. [Visit NASPS](https://nasps-sturgeon.org/about/).\\n\\n3. **U.S. Fish and Wildlife Service on Lake Sturgeon and the Endangered Species Act**:\\n - The U.S. Fish and Wildlife Service determined that lake sturgeon do not require listing under the Endangered Species Act, thanks to ongoing management efforts such as fish stocking that have contributed to their population stability. [Read the press release](https://www.fws.gov/press-release/2024-04/collaborative-conservation-keeps-lake-sturgeon-endangered-list).\\n\\n4. **Conservation Genetics of Atlantic Sturgeon (USGS)**:\\n - The USGS is conducting research on the conservation genetics of Atlantic sturgeon, with a focus on genetic assignment testing and population genetic studies. This is in response to the rediscovery of populations that were previously thought to be extirpated, necessitating updated management strategies. [Learn more from USGS](https://www.usgs.gov/centers/eesc/science/conservation-genetics-atlantic-sturgeon).\\n\\n5. **Restoration Efforts Along the Ontonagon River and St. Louis River**:\\n - The Iron River National Fish Hatchery and Ashland Fish and Wildlife Conservation Office are working with partners to restore lake sturgeon in Michigan and Minnesota. Efforts include collecting larval sturgeon and milt for breeding and stocking programs. [Read the story](https://www.fws.gov/story/2024-04/restoring-reverence-along-lake-sturgeon).\\n\\nThese resources provide comprehensive data on the state of North American sturgeon species and the various conservation efforts being undertaken to preserve and enhance their populations. If you require further information or assistance, feel free to ask.')]}}\n",
|
||||
"---\n",
|
||||
"{'supervisor': {'next': 'ResearchTeam'}}\n",
|
||||
"---\n",
|
||||
"{'ResearchTeam': {'messages': [HumanMessage(content='Based on the search results obtained, here is a summary of the information available regarding the conservation status, size, lifespan, and conservation efforts for North American sturgeon species:\\n\\n1. **Reconnecting Fragmented Sturgeon Populations in North American Rivers (Jager et al.)**:\\n - This paper discusses the fragmentation of large North American rivers by dams that interrupt the migrations of wide-ranging fishes like sturgeons. Efforts to reconnect habitats are viewed as crucial for protecting sturgeon species in U.S. rivers, as these species have lost between 5% and 60% of their historical ranges. [Learn more from the paper](https://www.semanticscholar.org/paper/Reconnecting-Fragmented-Sturgeon-Populations-in-Jager-Parsley/45414d7c86cd2d4f04b9490c7143f36b5158e729/figure/0).\\n\\n2. **North American Sturgeon and Paddlefish Society (NASPS)**:\\n - NASPS is dedicated to promoting the conservation and restoration of sturgeon species in North America by developing and advancing research pertaining to their biology, management, and utilization. [Visit NASPS](https://nasps-sturgeon.org/about/).\\n\\n3. **U.S. Fish and Wildlife Service on Lake Sturgeon and the Endangered Species Act**:\\n - The U.S. Fish and Wildlife Service determined that lake sturgeon do not require listing under the Endangered Species Act, thanks to ongoing management efforts such as fish stocking that have contributed to their population stability. [Read the press release](https://www.fws.gov/press-release/2024-04/collaborative-conservation-keeps-lake-sturgeon-endangered-list).\\n\\n4. **Conservation Genetics of Atlantic Sturgeon (USGS)**:\\n - The USGS is conducting research on the conservation genetics of Atlantic sturgeon, with a focus on genetic assignment testing and population genetic studies. This is in response to the rediscovery of populations that were previously thought to be extirpated, necessitating updated management strategies. [Learn more from USGS](https://www.usgs.gov/centers/eesc/science/conservation-genetics-atlantic-sturgeon).\\n\\n5. **Restoration Efforts Along the Ontonagon River and St. Louis River**:\\n - The Iron River National Fish Hatchery and Ashland Fish and Wildlife Conservation Office are working with partners to restore lake sturgeon in Michigan and Minnesota. Efforts include collecting larval sturgeon and milt for breeding and stocking programs. [Read the story](https://www.fws.gov/story/2024-04/restoring-reverence-along-lake-sturgeon).\\n\\nThese resources provide comprehensive data on the state of North American sturgeon species and the various conservation efforts being undertaken to preserve and enhance their populations. If you require further information or assistance, feel free to ask.')]}}\n",
|
||||
"---\n",
|
||||
"{'supervisor': {'next': 'ResearchTeam'}}\n",
|
||||
"---\n",
|
||||
"{'ResearchTeam': {'messages': [HumanMessage(content='Based on the search results obtained, here is a summary of the information available regarding the conservation status, size, lifespan, and conservation efforts for North American sturgeon species:\\n\\n1. **Reconnecting Fragmented Sturgeon Populations in North American Rivers (Jager et al.)**:\\n - This paper discusses the fragmentation of large North American rivers by dams that interrupt the migrations of wide-ranging fishes like sturgeons. Efforts to reconnect habitats are viewed as crucial for protecting sturgeon species in U.S. rivers, as these species have lost between 5% and 60% of their historical ranges. [Learn more from the paper](https://www.semanticscholar.org/paper/Reconnecting-Fragmented-Sturgeon-Populations-in-Jager-Parsley/45414d7c86cd2d4f04b9490c7143f36b5158e729/figure/0).\\n\\n2. **North American Sturgeon and Paddlefish Society (NASPS)**:\\n - NASPS is dedicated to promoting the conservation and restoration of sturgeon species in North America by developing and advancing research pertaining to their biology, management, and utilization. [Visit NASPS](https://nasps-sturgeon.org/about/).\\n\\n3. **U.S. Fish and Wildlife Service on Lake Sturgeon and the Endangered Species Act**:\\n - The U.S. Fish and Wildlife Service determined that lake sturgeon do not require listing under the Endangered Species Act, thanks to ongoing management efforts such as fish stocking that have contributed to their population stability. [Read the press release](https://www.fws.gov/press-release/2024-04/collaborative-conservation-keeps-lake-sturgeon-endangered-list).\\n\\n4. **Conservation Genetics of Atlantic Sturgeon (USGS)**:\\n - The USGS is conducting research on the conservation genetics of Atlantic sturgeon, with a focus on genetic assignment testing and population genetic studies. This is in response to the rediscovery of populations that were previously thought to be extirpated, necessitating updated management strategies. [Learn more from USGS](https://www.usgs.gov/centers/eesc/science/conservation-genetics-atlantic-sturgeon).\\n\\n5. **Restoration Efforts Along the Ontonagon River and St. Louis River**:\\n - The Iron River National Fish Hatchery and Ashland Fish and Wildlife Conservation Office are working with partners to restore lake sturgeon in Michigan and Minnesota. Efforts include collecting larval sturgeon and milt for breeding and stocking programs. [Read the story](https://www.fws.gov/story/2024-04/restoring-reverence-along-lake-sturgeon).\\n\\nThese resources provide comprehensive data on the state of North American sturgeon species and the various conservation efforts being undertaken to preserve and enhance their populations. If you require further information or assistance, feel free to ask.')]}}\n",
|
||||
"---\n",
|
||||
"{'supervisor': {'next': 'ResearchTeam'}}\n",
|
||||
"---\n",
|
||||
"{'ResearchTeam': {'messages': [HumanMessage(content='Based on the search results obtained, here is a summary of the information available regarding the conservation status, size, lifespan, and conservation efforts for North American sturgeon species:\\n\\n1. **Reconnecting Fragmented Sturgeon Populations in North American Rivers (Jager et al.)**:\\n - This paper discusses the fragmentation of large North American rivers by dams that interrupt the migrations of wide-ranging fishes like sturgeons. Efforts to reconnect habitats are viewed as crucial for protecting sturgeon species in U.S. rivers, as these species have lost between 5% and 60% of their historical ranges. [Learn more from the paper](https://www.semanticscholar.org/paper/Reconnecting-Fragmented-Sturgeon-Populations-in-Jager-Parsley/45414d7c86cd2d4f04b9490c7143f36b5158e729/figure/0).\\n\\n2. **North American Sturgeon and Paddlefish Society (NASPS)**:\\n - NASPS is dedicated to promoting the conservation and restoration of sturgeon species in North America by developing and advancing research pertaining to their biology, management, and utilization. [Visit NASPS](https://nasps-sturgeon.org/about/).\\n\\n3. **U.S. Fish and Wildlife Service on Lake Sturgeon and the Endangered Species Act**:\\n - The U.S. Fish and Wildlife Service determined that lake sturgeon do not require listing under the Endangered Species Act, thanks to ongoing management efforts such as fish stocking that have contributed to their population stability. [Read the press release](https://www.fws.gov/press-release/2024-04/collaborative-conservation-keeps-lake-sturgeon-endangered-list).\\n\\n4. **Conservation Genetics of Atlantic Sturgeon (USGS)**:\\n - The USGS is conducting research on the conservation genetics of Atlantic sturgeon, with a focus on genetic assignment testing and population genetic studies. This is in response to the rediscovery of populations that were previously thought to be extirpated, necessitating updated management strategies. [Learn more from USGS](https://www.usgs.gov/centers/eesc/science/conservation-genetics-atlantic-sturgeon).\\n\\n5. **Restoration Efforts Along the Ontonagon River and St. Louis River**:\\n - The Iron River National Fish Hatchery and Ashland Fish and Wildlife Conservation Office are working with partners to restore lake sturgeon in Michigan and Minnesota. Efforts include collecting larval sturgeon and milt for breeding and stocking programs. [Read the story](https://www.fws.gov/story/2024-04/restoring-reverence-along-lake-sturgeon).\\n\\nThese resources provide comprehensive data on the state of North American sturgeon species and the various conservation efforts being undertaken to preserve and enhance their populations. If you require further information or assistance, feel free to ask.')]}}\n",
|
||||
"---\n",
|
||||
"{'supervisor': {'next': 'ResearchTeam'}}\n",
|
||||
"---\n",
|
||||
"{'ResearchTeam': {'messages': [HumanMessage(content='Based on the search results obtained, here is a summary of the information available regarding the conservation status, size, lifespan, and conservation efforts for North American sturgeon species:\\n\\n1. **Reconnecting Fragmented Sturgeon Populations in North American Rivers (Jager et al.)**:\\n - This paper discusses the fragmentation of large North American rivers by dams that interrupt the migrations of wide-ranging fishes like sturgeons. Efforts to reconnect habitats are viewed as crucial for protecting sturgeon species in U.S. rivers, as these species have lost between 5% and 60% of their historical ranges. [Learn more from the paper](https://www.semanticscholar.org/paper/Reconnecting-Fragmented-Sturgeon-Populations-in-Jager-Parsley/45414d7c86cd2d4f04b9490c7143f36b5158e729/figure/0).\\n\\n2. **North American Sturgeon and Paddlefish Society (NASPS)**:\\n - NASPS is dedicated to promoting the conservation and restoration of sturgeon species in North America by developing and advancing research pertaining to their biology, management, and utilization. [Visit NASPS](https://nasps-sturgeon.org/about/).\\n\\n3. **U.S. Fish and Wildlife Service on Lake Sturgeon and the Endangered Species Act**:\\n - The U.S. Fish and Wildlife Service determined that lake sturgeon do not require listing under the Endangered Species Act, thanks to ongoing management efforts such as fish stocking that have contributed to their population stability. [Read the press release](https://www.fws.gov/press-release/2024-04/collaborative-conservation-keeps-lake-sturgeon-endangered-list).\\n\\n4. **Conservation Genetics of Atlantic Sturgeon (USGS)**:\\n - The USGS is conducting research on the conservation genetics of Atlantic sturgeon, with a focus on genetic assignment testing and population genetic studies. This is in response to the rediscovery of populations that were previously thought to be extirpated, necessitating updated management strategies. [Learn more from USGS](https://www.usgs.gov/centers/eesc/science/conservation-genetics-atlantic-sturgeon).\\n\\n5. **Restoration Efforts Along the Ontonagon River and St. Louis River**:\\n - The Iron River National Fish Hatchery and Ashland Fish and Wildlife Conservation Office are working with partners to restore lake sturgeon in Michigan and Minnesota. Efforts include collecting larval sturgeon and milt for breeding and stocking programs. [Read the story](https://www.fws.gov/story/2024-04/restoring-reverence-along-lake-sturgeon).\\n\\nThese resources provide comprehensive data on the state of North American sturgeon species and the various conservation efforts being undertaken to preserve and enhance their populations. If you require further information or assistance, feel free to ask.')]}}\n",
|
||||
"---\n",
|
||||
"{'supervisor': {'next': 'ResearchTeam'}}\n",
|
||||
"---\n",
|
||||
"{'ResearchTeam': {'messages': [HumanMessage(content='Based on the search results obtained, here is a summary of the information available regarding the conservation status, size, lifespan, and conservation efforts for North American sturgeon species:\\n\\n1. **Reconnecting Fragmented Sturgeon Populations in North American Rivers (Jager et al.)**:\\n - This paper discusses the fragmentation of large North American rivers by dams that interrupt the migrations of wide-ranging fishes like sturgeons. Efforts to reconnect habitats are viewed as crucial for protecting sturgeon species in U.S. rivers, as these species have lost between 5% and 60% of their historical ranges. [Learn more from the paper](https://www.semanticscholar.org/paper/Reconnecting-Fragmented-Sturgeon-Populations-in-Jager-Parsley/45414d7c86cd2d4f04b9490c7143f36b5158e729/figure/0).\\n\\n2. **North American Sturgeon and Paddlefish Society (NASPS)**:\\n - NASPS is dedicated to promoting the conservation and restoration of sturgeon species in North America by developing and advancing research pertaining to their biology, management, and utilization. [Visit NASPS](https://nasps-sturgeon.org/about/).\\n\\n3. **U.S. Fish and Wildlife Service on Lake Sturgeon and the Endangered Species Act**:\\n - The U.S. Fish and Wildlife Service determined that lake sturgeon do not require listing under the Endangered Species Act, thanks to ongoing management efforts such as fish stocking that have contributed to their population stability. [Read the press release](https://www.fws.gov/press-release/2024-04/collaborative-conservation-keeps-lake-sturgeon-endangered-list).\\n\\n4. **Conservation Genetics of Atlantic Sturgeon (USGS)**:\\n - The USGS is conducting research on the conservation genetics of Atlantic sturgeon, with a focus on genetic assignment testing and population genetic studies. This is in response to the rediscovery of populations that were previously thought to be extirpated, necessitating updated management strategies. [Learn more from USGS](https://www.usgs.gov/centers/eesc/science/conservation-genetics-atlantic-sturgeon).\\n\\n5. **Restoration Efforts Along the Ontonagon River and St. Louis River**:\\n - The Iron River National Fish Hatchery and Ashland Fish and Wildlife Conservation Office are working with partners to restore lake sturgeon in Michigan and Minnesota. Efforts include collecting larval sturgeon and milt for breeding and stocking programs. [Read the story](https://www.fws.gov/story/2024-04/restoring-reverence-along-lake-sturgeon).\\n\\nThese resources provide comprehensive data on the state of North American sturgeon species and the various conservation efforts being undertaken to preserve and enhance their populations. If you require further information or assistance, feel free to ask.')]}}\n",
|
||||
"---\n",
|
||||
"{'supervisor': {'next': 'ResearchTeam'}}\n",
|
||||
"---\n",
|
||||
"{'ResearchTeam': {'messages': [HumanMessage(content='Based on the search results obtained, here is a summary of the information available regarding the conservation status, size, lifespan, and conservation efforts for North American sturgeon species:\\n\\n1. **Reconnecting Fragmented Sturgeon Populations in North American Rivers (Jager et al.)**:\\n - This paper discusses the fragmentation of large North American rivers by dams that interrupt the migrations of wide-ranging fishes like sturgeons. Efforts to reconnect habitats are viewed as crucial for protecting sturgeon species in U.S. rivers, as these species have lost between 5% and 60% of their historical ranges. [Learn more from the paper](https://www.semanticscholar.org/paper/Reconnecting-Fragmented-Sturgeon-Populations-in-Jager-Parsley/45414d7c86cd2d4f04b9490c7143f36b5158e729/figure/0).\\n\\n2. **North American Sturgeon and Paddlefish Society (NASPS)**:\\n - NASPS is dedicated to promoting the conservation and restoration of sturgeon species in North America by developing and advancing research pertaining to their biology, management, and utilization. [Visit NASPS](https://nasps-sturgeon.org/about/).\\n\\n3. **U.S. Fish and Wildlife Service on Lake Sturgeon and the Endangered Species Act**:\\n - The U.S. Fish and Wildlife Service determined that lake sturgeon do not require listing under the Endangered Species Act, thanks to ongoing management efforts such as fish stocking that have contributed to their population stability. [Read the press release](https://www.fws.gov/press-release/2024-04/collaborative-conservation-keeps-lake-sturgeon-endangered-list).\\n\\n4. **Conservation Genetics of Atlantic Sturgeon (USGS)**:\\n - The USGS is conducting research on the conservation genetics of Atlantic sturgeon, with a focus on genetic assignment testing and population genetic studies. This is in response to the rediscovery of populations that were previously thought to be extirpated, necessitating updated management strategies. [Learn more from USGS](https://www.usgs.gov/centers/eesc/science/conservation-genetics-atlantic-sturgeon).\\n\\n5. **Restoration Efforts Along the Ontonagon River and St. Louis River**:\\n - The Iron River National Fish Hatchery and Ashland Fish and Wildlife Conservation Office are working with partners to restore lake sturgeon in Michigan and Minnesota. Efforts include collecting larval sturgeon and milt for breeding and stocking programs. [Read the story](https://www.fws.gov/story/2024-04/restoring-reverence-along-lake-sturgeon).\\n\\nThese resources provide comprehensive data on the state of North American sturgeon species and the various conservation efforts being undertaken to preserve and enhance their populations. If you require further information or assistance, feel free to ask.')]}}\n",
|
||||
"---\n",
|
||||
"{'supervisor': {'next': 'ResearchTeam'}}\n",
|
||||
"---\n",
|
||||
"{'ResearchTeam': {'messages': [HumanMessage(content='Based on the search results obtained, here is a summary of the information available regarding the conservation status, size, lifespan, and conservation efforts for North American sturgeon species:\\n\\n1. **Reconnecting Fragmented Sturgeon Populations in North American Rivers (Jager et al.)**:\\n - This paper discusses the fragmentation of large North American rivers by dams that interrupt the migrations of wide-ranging fishes like sturgeons. Efforts to reconnect habitats are viewed as crucial for protecting sturgeon species in U.S. rivers, as these species have lost between 5% and 60% of their historical ranges. [Learn more from the paper](https://www.semanticscholar.org/paper/Reconnecting-Fragmented-Sturgeon-Populations-in-Jager-Parsley/45414d7c86cd2d4f04b9490c7143f36b5158e729/figure/0).\\n\\n2. **North American Sturgeon and Paddlefish Society (NASPS)**:\\n - NASPS is dedicated to promoting the conservation and restoration of sturgeon species in North America by developing and advancing research pertaining to their biology, management, and utilization. [Visit NASPS](https://nasps-sturgeon.org/about/).\\n\\n3. **U.S. Fish and Wildlife Service on Lake Sturgeon and the Endangered Species Act**:\\n - The U.S. Fish and Wildlife Service determined that lake sturgeon do not require listing under the Endangered Species Act, thanks to ongoing management efforts such as fish stocking that have contributed to their population stability. [Read the press release](https://www.fws.gov/press-release/2024-04/collaborative-conservation-keeps-lake-sturgeon-endangered-list).\\n\\n4. **Conservation Genetics of Atlantic Sturgeon (USGS)**:\\n - The USGS is conducting research on the conservation genetics of Atlantic sturgeon, with a focus on genetic assignment testing and population genetic studies. This is in response to the rediscovery of populations that were previously thought to be extirpated, necessitating updated management strategies. [Learn more from USGS](https://www.usgs.gov/centers/eesc/science/conservation-genetics-atlantic-sturgeon).\\n\\n5. **Restoration Efforts Along the Ontonagon River and St. Louis River**:\\n - The Iron River National Fish Hatchery and Ashland Fish and Wildlife Conservation Office are working with partners to restore lake sturgeon in Michigan and Minnesota. Efforts include collecting larval sturgeon and milt for breeding and stocking programs. [Read the story](https://www.fws.gov/story/2024-04/restoring-reverence-along-lake-sturgeon).\\n\\nThese resources provide comprehensive data on the state of North American sturgeon species and the various conservation efforts being undertaken to preserve and enhance their populations. If you require further information or assistance, feel free to ask.')]}}\n",
|
||||
"---\n",
|
||||
"{'supervisor': {'next': 'ResearchTeam'}}\n",
|
||||
"---\n",
|
||||
"{'ResearchTeam': {'messages': [HumanMessage(content='Based on the search results obtained, here is a summary of the information available regarding the conservation status, size, lifespan, and conservation efforts for North American sturgeon species:\\n\\n1. **Reconnecting Fragmented Sturgeon Populations in North American Rivers (Jager et al.)**:\\n - This paper discusses the fragmentation of large North American rivers by dams that interrupt the migrations of wide-ranging fishes like sturgeons. Efforts to reconnect habitats are viewed as crucial for protecting sturgeon species in U.S. rivers, as these species have lost between 5% and 60% of their historical ranges. [Learn more from the paper](https://www.semanticscholar.org/paper/Reconnecting-Fragmented-Sturgeon-Populations-in-Jager-Parsley/45414d7c86cd2d4f04b9490c7143f36b5158e729/figure/0).\\n\\n2. **North American Sturgeon and Paddlefish Society (NASPS)**:\\n - NASPS is dedicated to promoting the conservation and restoration of sturgeon species in North America by developing and advancing research pertaining to their biology, management, and utilization. [Visit NASPS](https://nasps-sturgeon.org/about/).\\n\\n3. **U.S. Fish and Wildlife Service on Lake Sturgeon and the Endangered Species Act**:\\n - The U.S. Fish and Wildlife Service determined that lake sturgeon do not require listing under the Endangered Species Act, thanks to ongoing management efforts such as fish stocking that have contributed to their population stability. [Read the press release](https://www.fws.gov/press-release/2024-04/collaborative-conservation-keeps-lake-sturgeon-endangered-list).\\n\\n4. **Conservation Genetics of Atlantic Sturgeon (USGS)**:\\n - The USGS is conducting research on the conservation genetics of Atlantic sturgeon, with a focus on genetic assignment testing and population genetic studies. This is in response to the rediscovery of populations that were previously thought to be extirpated, necessitating updated management strategies. [Learn more from USGS](https://www.usgs.gov/centers/eesc/science/conservation-genetics-atlantic-sturgeon).\\n\\n5. **Restoration Efforts Along the Ontonagon River and St. Louis River**:\\n - The Iron River National Fish Hatchery and Ashland Fish and Wildlife Conservation Office are working with partners to restore lake sturgeon in Michigan and Minnesota. Efforts include collecting larval sturgeon and milt for breeding and stocking programs. [Read the story](https://www.fws.gov/story/2024-04/restoring-reverence-along-lake-sturgeon).\\n\\nThese resources provide comprehensive data on the state of North American sturgeon species and the various conservation efforts being undertaken to preserve and enhance their populations. If you require further information or assistance, feel free to ask.')]}}\n",
|
||||
"---\n",
|
||||
"{'supervisor': {'next': 'ResearchTeam'}}\n",
|
||||
"---\n",
|
||||
"{'ResearchTeam': {'messages': [HumanMessage(content='Based on the search results obtained, here is a summary of the information available regarding the conservation status, size, lifespan, and conservation efforts for North American sturgeon species:\\n\\n1. **Reconnecting Fragmented Sturgeon Populations in North American Rivers (Jager et al.)**:\\n - This paper discusses the fragmentation of large North American rivers by dams that interrupt the migrations of wide-ranging fishes like sturgeons. Efforts to reconnect habitats are viewed as crucial for protecting sturgeon species in U.S. rivers, as these species have lost between 5% and 60% of their historical ranges. [Learn more from the paper](https://www.semanticscholar.org/paper/Reconnecting-Fragmented-Sturgeon-Populations-in-Jager-Parsley/45414d7c86cd2d4f04b9490c7143f36b5158e729/figure/0).\\n\\n2. **North American Sturgeon and Paddlefish Society (NASPS)**:\\n - NASPS is dedicated to promoting the conservation and restoration of sturgeon species in North America by developing and advancing research pertaining to their biology, management, and utilization. [Visit NASPS](https://nasps-sturgeon.org/about/).\\n\\n3. **U.S. Fish and Wildlife Service on Lake Sturgeon and the Endangered Species Act**:\\n - The U.S. Fish and Wildlife Service determined that lake sturgeon do not require listing under the Endangered Species Act, thanks to ongoing management efforts such as fish stocking that have contributed to their population stability. [Read the press release](https://www.fws.gov/press-release/2024-04/collaborative-conservation-keeps-lake-sturgeon-endangered-list).\\n\\n4. **Conservation Genetics of Atlantic Sturgeon (USGS)**:\\n - The USGS is conducting research on the conservation genetics of Atlantic sturgeon, with a focus on genetic assignment testing and population genetic studies. This is in response to the rediscovery of populations that were previously thought to be extirpated, necessitating updated management strategies. [Learn more from USGS](https://www.usgs.gov/centers/eesc/science/conservation-genetics-atlantic-sturgeon).\\n\\n5. **Restoration Efforts Along the Ontonagon River and St. Louis River**:\\n - The Iron River National Fish Hatchery and Ashland Fish and Wildlife Conservation Office are working with partners to restore lake sturgeon in Michigan and Minnesota. Efforts include collecting larval sturgeon and milt for breeding and stocking programs. [Read the story](https://www.fws.gov/story/2024-04/restoring-reverence-along-lake-sturgeon).\\n\\nThese resources provide comprehensive data on the state of North American sturgeon species and the various conservation efforts being undertaken to preserve and enhance their populations. If you require further information or assistance, feel free to ask.')]}}\n",
|
||||
"---\n",
|
||||
"{'supervisor': {'next': 'ResearchTeam'}}\n",
|
||||
"---\n",
|
||||
"{'ResearchTeam': {'messages': [HumanMessage(content='Based on the search results obtained, here is a summary of the information available regarding the conservation status, size, lifespan, and conservation efforts for North American sturgeon species:\\n\\n**Conservation Status of North American Sturgeon:**\\n- Lake sturgeon has origins dating back at least 150 million years and is one of the largest freshwater fish in North America. They are not currently listed under the Endangered Species Act, thanks to conservation efforts such as fish stocking ([U.S. Fish and Wildlife Service Press Release](https://www.fws.gov/press-release/2024-04/collaborative-conservation-keeps-lake-sturgeon-endangered-list)).\\n- The North American Sturgeon and Paddlefish Society (NASPS) is dedicated to promoting the conservation and restoration of sturgeon species in North America by developing research on their biology, management, and utilization ([NASPS About](https://nasps-sturgeon.org/about/)).\\n- All 26 remaining sturgeon species are now threatened with extinction according to the IUCN ([WWF News](https://wwf.panda.org/wwf_news/?6080466/sturgeon-slipping-towards-extinction)).\\n- The USGS is conducting research on the conservation genetics of Atlantic sturgeon to ensure appropriate management strategies can be developed ([USGS Conservation Genetics](https://www.usgs.gov/centers/eesc/science/conservation-genetics-atlantic-sturgeon)).\\n\\n**Size and Lifespan of North American Sturgeon Species:**\\n- The white sturgeon (Acipenser transmontanus), also known as the Pacific sturgeon, can grow up to 20 feet long and weigh up to 1,800 pounds. It is the largest freshwater fish in North America ([American Oceans](https://www.americanoceans.org/facts/types-of-sturgeon/)).\\n- The shortnose sturgeon (Acipenser brevirostrum) can grow up to 4 feet long and weigh up to 50 pounds. It inhabits the eastern coast of North America ([American Oceans](https://www.americanoceans.org/facts/types-of-sturgeon/)).\\n- The green sturgeon (Acipenser medirostris) can reach up to 7 feet long and weigh up to 350 pounds ([American Oceans](https://www.americanoceans.org/facts/types-of-sturgeon/)).\\n\\n**Conservation Efforts for North American Sturgeon Species:**\\n- Long-term conservation efforts in North America have helped to stabilize and increase some sturgeon populations, such as the white sturgeon in the Fraser River in the U.S. ([WWF News](https://wwf.panda.org/wwf_news/?6080466/sturgeon-slipping-towards-extinction)).\\n- The NASPS works to foster the conservation of sturgeon species and restoration of sturgeon stocks in North America ([NASPS About](https://nasps-sturgeon.org/about/)).\\n- The collaborative conservation efforts, including fish stocking, have contributed to the conservation and resiliency of lake sturgeon ([U.S. Fish and Wildlife Service Press Release](https://www.fws.gov/press-release/2024-04/collaborative-conservation-keeps-lake-sturgeon-endangered-list)).\\n- Work in the Chesapeake Bay includes identifying and protecting habitat used by Atlantic sturgeon for spawning, seeking to minimize vessel strikes, and educating students about these fish ([NOAA Fisheries](https://www.fisheries.noaa.gov/feature-story/supporting-endangered-atlantic-sturgeon-chesapeake-bay)).\\n\\nThese resources provide comprehensive data on the state of North American sturgeon species and the various conservation efforts being undertaken to preserve and enhance their populations. If you require further information or assistance, feel free to ask.', name='Search')]}}\n",
|
||||
"---\n",
|
||||
"{'supervisor': {'next': 'ResearchTeam'}}\n",
|
||||
"---\n",
|
||||
"{'ResearchTeam': {'messages': [HumanMessage(content='Based on the search results obtained, here is a summary of the information available regarding the conservation status, size, lifespan, and conservation efforts for North American sturgeon species:\\n\\n**Conservation Status of North American Sturgeon:**\\n- Lake sturgeon has origins dating back at least 150 million years and is one of the largest freshwater fish in North America. They are not currently listed under the Endangered Species Act, thanks to conservation efforts such as fish stocking ([U.S. Fish and Wildlife Service Press Release](https://www.fws.gov/press-release/2024-04/collaborative-conservation-keeps-lake-sturgeon-endangered-list)).\\n- The North American Sturgeon and Paddlefish Society (NASPS) is dedicated to promoting the conservation and restoration of sturgeon species in North America by developing research on their biology, management, and utilization ([NASPS About](https://nasps-sturgeon.org/about/)).\\n- All 26 remaining sturgeon species are now threatened with extinction according to the IUCN ([WWF News](https://wwf.panda.org/wwf_news/?6080466/sturgeon-slipping-towards-extinction)).\\n- The USGS is conducting research on the conservation genetics of Atlantic sturgeon to ensure appropriate management strategies can be developed ([USGS Conservation Genetics](https://www.usgs.gov/centers/eesc/science/conservation-genetics-atlantic-sturgeon)).\\n\\n**Size and Lifespan of North American Sturgeon Species:**\\n- The white sturgeon (Acipenser transmontanus), also known as the Pacific sturgeon, can grow up to 20 feet long and weigh up to 1,800 pounds. It is the largest freshwater fish in North America ([American Oceans](https://www.americanoceans.org/facts/types-of-sturgeon/)).\\n- The shortnose sturgeon (Acipenser brevirostrum) can grow up to 4 feet long and weigh up to 50 pounds. It inhabits the eastern coast of North America ([American Oceans](https://www.americanoceans.org/facts/types-of-sturgeon/)).\\n- The green sturgeon (Acipenser medirostris) can reach up to 7 feet long and weigh up to 350 pounds ([American Oceans](https://www.americanoceans.org/facts/types-of-sturgeon/)).\\n\\n**Conservation Efforts for North American Sturgeon Species:**\\n- Long-term conservation efforts in North America have helped to stabilize and increase some sturgeon populations, such as the white sturgeon in the Fraser River in the U.S. ([WWF News](https://wwf.panda.org/wwf_news/?6080466/sturgeon-slipping-towards-extinction)).\\n- The NASPS works to foster the conservation of sturgeon species and restoration of sturgeon stocks in North America ([NASPS About](https://nasps-sturgeon.org/about/)).\\n- The collaborative conservation efforts, including fish stocking, have contributed to the conservation and resiliency of lake sturgeon ([U.S. Fish and Wildlife Service Press Release](https://www.fws.gov/press-release/2024-04/collaborative-conservation-keeps-lake-sturgeon-endangered-list)).\\n- Work in the Chesapeake Bay includes identifying and protecting habitat used by Atlantic sturgeon for spawning, seeking to minimize vessel strikes, and educating students about these fish ([NOAA Fisheries](https://www.fisheries.noaa.gov/feature-story/supporting-endangered-atlantic-sturgeon-chesapeake-bay)).\\n\\nThese resources provide comprehensive data on the state of North American sturgeon species and the various conservation efforts being undertaken to preserve and enhance their populations. If you require further information or assistance, feel free to ask.')]}}\n",
|
||||
"---\n",
|
||||
"{'supervisor': {'next': 'ResearchTeam'}}\n",
|
||||
"---\n",
|
||||
"{'ResearchTeam': {'messages': [HumanMessage(content='Based on the search results obtained, here is a summary of the information available regarding the conservation status, size, lifespan, and conservation efforts for North American sturgeon species:\\n\\n**Conservation Status of North American Sturgeon:**\\n- Lake sturgeon has origins dating back at least 150 million years and is one of the largest freshwater fish in North America. They are not currently listed under the Endangered Species Act, thanks to conservation efforts such as fish stocking ([U.S. Fish and Wildlife Service Press Release](https://www.fws.gov/press-release/2024-04/collaborative-conservation-keeps-lake-sturgeon-endangered-list)).\\n- The North American Sturgeon and Paddlefish Society (NASPS) is dedicated to promoting the conservation and restoration of sturgeon species in North America by developing research on their biology, management, and utilization ([NASPS About](https://nasps-sturgeon.org/about/)).\\n- All 26 remaining sturgeon species are now threatened with extinction according to the IUCN ([WWF News](https://wwf.panda.org/wwf_news/?6080466/sturgeon-slipping-towards-extinction)).\\n- The USGS is conducting research on the conservation genetics of Atlantic sturgeon to ensure appropriate management strategies can be developed ([USGS Conservation Genetics](https://www.usgs.gov/centers/eesc/science/conservation-genetics-atlantic-sturgeon)).\\n\\n**Size and Lifespan of North American Sturgeon Species:**\\n- The white sturgeon (Acipenser transmontanus), also known as the Pacific sturgeon, can grow up to 20 feet long and weigh up to 1,800 pounds. It is the largest freshwater fish in North America ([American Oceans](https://www.americanoceans.org/facts/types-of-sturgeon/)).\\n- The shortnose sturgeon (Acipenser brevirostrum) can grow up to 4 feet long and weigh up to 50 pounds. It inhabits the eastern coast of North America ([American Oceans](https://www.americanoceans.org/facts/types-of-sturgeon/)).\\n- The green sturgeon (Acipenser medirostris) can reach up to 7 feet long and weigh up to 350 pounds ([American Oceans](https://www.americanoceans.org/facts/types-of-sturgeon/)).\\n\\n**Conservation Efforts for North American Sturgeon Species:**\\n- Long-term conservation efforts in North America have helped to stabilize and increase some sturgeon populations, such as the white sturgeon in the Fraser River in the U.S. ([WWF News](https://wwf.panda.org/wwf_news/?6080466/sturgeon-slipping-towards-extinction)).\\n- The NASPS works to foster the conservation of sturgeon species and restoration of sturgeon stocks in North America ([NASPS About](https://nasps-sturgeon.org/about/)).\\n- The collaborative conservation efforts, including fish stocking, have contributed to the conservation and resiliency of lake sturgeon ([U.S. Fish and Wildlife Service Press Release](https://www.fws.gov/press-release/2024-04/collaborative-conservation-keeps-lake-sturgeon-endangered-list)).\\n- Work in the Chesapeake Bay includes identifying and protecting habitat used by Atlantic sturgeon for spawning, seeking to minimize vessel strikes, and educating students about these fish ([NOAA Fisheries](https://www.fisheries.noaa.gov/feature-story/supporting-endangered-atlantic-sturgeon-chesapeake-bay)).\\n\\nThese resources provide comprehensive data on the state of North American sturgeon species and the various conservation efforts being undertaken to preserve and enhance their populations. If you require further information or assistance, feel free to ask.')]}}\n",
|
||||
"---\n",
|
||||
"{'supervisor': {'next': 'PaperWritingTeam'}}\n",
|
||||
"---\n",
|
||||
"{'PaperWritingTeam': {'messages': [HumanMessage(content='The outline for North American sturgeon species, including their conservation status, size, lifespan, and conservation efforts, has been successfully created and saved to a file named \"North_American_Sturgeon_Overview\". If you need to review the document or require additional information, please let me know.', name='NoteTaker')]}}\n",
|
||||
"---\n",
|
||||
"{'supervisor': {'next': 'PaperWritingTeam'}}\n",
|
||||
"---\n",
|
||||
"{'PaperWritingTeam': {'messages': [HumanMessage(content='The document \"North_American_Sturgeon_Overview\" contains the following outline:\\n\\n1. Conservation Status of North American Sturgeon\\n2. Size and Lifespan of North American Sturgeon Species\\n3. Conservation Efforts for North American Sturgeon Species\\n\\nIf you need more detailed information on any of these topics or have any other requests related to the document, feel free to let me know!', name='DocWriter')]}}\n",
|
||||
"---\n",
|
||||
"{'supervisor': {'next': 'ResearchTeam'}}\n",
|
||||
"---\n",
|
||||
"{'ResearchTeam': {'messages': [HumanMessage(content='The document \"North_American_Sturgeon_Overview\" contains the following outline:\\n\\n1. Conservation Status of North American Sturgeon\\n2. Size and Lifespan of North American Sturgeon Species\\n3. Conservation Efforts for North American Sturgeon Species\\n\\nIf you need more detailed information on any of these topics or have any other requests related to the document, feel free to let me know!')]}}\n",
|
||||
"---\n",
|
||||
"{'supervisor': {'next': 'ResearchTeam'}}\n",
|
||||
"---\n",
|
||||
"{'ResearchTeam': {'messages': [HumanMessage(content='The document \"North_American_Sturgeon_Overview\" contains the following outline:\\n\\n1. Conservation Status of North American Sturgeon\\n2. Size and Lifespan of North American Sturgeon Species\\n3. Conservation Efforts for North American Sturgeon Species\\n\\nIf you need more detailed information on any of these topics or have any other requests related to the document, feel free to let me know!')]}}\n",
|
||||
"---\n",
|
||||
"{'supervisor': {'next': 'ResearchTeam'}}\n",
|
||||
"---\n",
|
||||
"{'ResearchTeam': {'messages': [HumanMessage(content='The document \"North_American_Sturgeon_Overview\" contains the following outline:\\n\\n1. Conservation Status of North American Sturgeon\\n2. Size and Lifespan of North American Sturgeon Species\\n3. Conservation Efforts for North American Sturgeon Species\\n\\nIf you need more detailed information on any of these topics or have any other requests related to the document, feel free to let me know!')]}}\n",
|
||||
"---\n",
|
||||
"{'supervisor': {'next': 'PaperWritingTeam'}}\n",
|
||||
"---\n",
|
||||
"{'PaperWritingTeam': {'messages': [HumanMessage(content='It seems that the document \"North_American_Sturgeon_Overview\" contains exactly the outline provided earlier, with three main topics:\\n\\n1. Conservation Status of North American Sturgeon\\n2. Size and Lifespan of North American Sturgeon Species\\n3. Conservation Efforts for North American Sturgeon Species\\n\\nIf you would like to delve into any of these topics or have another request regarding the document, please let me know how I can assist you further!', name='NoteTaker')]}}\n",
|
||||
"---\n",
|
||||
"{'supervisor': {'next': 'ResearchTeam'}}\n",
|
||||
"---\n",
|
||||
"{'ResearchTeam': {'messages': [HumanMessage(content='It seems that the document \"North_American_Sturgeon_Overview\" contains exactly the outline provided earlier, with three main topics:\\n\\n1. Conservation Status of North American Sturgeon\\n2. Size and Lifespan of North American Sturgeon Species\\n3. Conservation Efforts for North American Sturgeon Species\\n\\nIf you would like to delve into any of these topics or have another request regarding the document, please let me know how I can assist you further!')]}}\n",
|
||||
"---\n",
|
||||
"{'supervisor': {'next': 'ResearchTeam'}}\n",
|
||||
"---\n",
|
||||
"{'ResearchTeam': {'messages': [HumanMessage(content='It seems that the document \"North_American_Sturgeon_Overview\" contains exactly the outline provided earlier, with three main topics:\\n\\n1. Conservation Status of North American Sturgeon\\n2. Size and Lifespan of North American Sturgeon Species\\n3. Conservation Efforts for North American Sturgeon Species\\n\\nIf you would like to delve into any of these topics or have another request regarding the document, please let me know how I can assist you further!')]}}\n",
|
||||
"---\n",
|
||||
"{'supervisor': {'next': 'PaperWritingTeam'}}\n",
|
||||
"---\n",
|
||||
"{'PaperWritingTeam': {'messages': [HumanMessage(content='The document \"North_American_Sturgeon_Overview\" indeed contains the three main topics outlined earlier:\\n\\n1. Conservation Status of North American Sturgeon\\n2. Size and Lifespan of North American Sturgeon Species\\n3. Conservation Efforts for North American Sturgeon Species\\n\\nIf you need detailed information on any of these topics or have another specific request related to the document, please let me know, and I can provide the information or take further action as needed.', name='NoteTaker')]}}\n",
|
||||
"---\n",
|
||||
"{'supervisor': {'next': 'ResearchTeam'}}\n",
|
||||
"---\n",
|
||||
"{'ResearchTeam': {'messages': [HumanMessage(content='The document \"North_American_Sturgeon_Overview\" indeed contains the three main topics outlined earlier:\\n\\n1. Conservation Status of North American Sturgeon\\n2. Size and Lifespan of North American Sturgeon Species\\n3. Conservation Efforts for North American Sturgeon Species\\n\\nIf you need detailed information on any of these topics or have another specific request related to the document, please let me know, and I can provide the information or take further action as needed.')]}}\n",
|
||||
"---\n",
|
||||
"{'supervisor': {'next': 'FINISH'}}\n",
|
||||
"{'supervisor': {'next': '__end__'}}\n",
|
||||
"---\n"
|
||||
]
|
||||
}
|
||||
@@ -1088,16 +826,13 @@
|
||||
"for s in super_graph.stream(\n",
|
||||
" {\n",
|
||||
" \"messages\": [\n",
|
||||
" HumanMessage(\n",
|
||||
" content=\"Write a brief research report on the North American sturgeon. Include a chart.\"\n",
|
||||
" )\n",
|
||||
" (\"user\", \"Research AI agents and write a brief report about them.\")\n",
|
||||
" ],\n",
|
||||
" },\n",
|
||||
" {\"recursion_limit\": 150},\n",
|
||||
"):\n",
|
||||
" if \"__end__\" not in s:\n",
|
||||
" print(s)\n",
|
||||
" print(\"---\")"
|
||||
" print(s)\n",
|
||||
" print(\"---\")"
|
||||
]
|
||||
}
|
||||
],
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
"cells": [
|
||||
{
|
||||
"attachments": {
|
||||
"02659c68-8b4b-42ed-a002-1c08f4a7c299.png": {
|
||||
"image/png": "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 truncated
|
||||
"7d0ca3af-e391-4d23-981a-dd640e08e4c1.png": {
|
||||
"image/png": "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 truncated
|
||||
}
|
||||
},
|
||||
"cell_type": "markdown",
|
||||
@@ -20,7 +20,7 @@
|
||||
"\n",
|
||||
"The resulting graph will look something like the following diagram:\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Before we get started, a quick note: this and other multi-agent notebooks are designed to show _how_ you can implement certain design patterns in LangGraph. If the pattern suits your needs, we recommend combining it with some of the other fundamental patterns described elsewhere in the docs for best performance.\n",
|
||||
"\n",
|
||||
@@ -37,12 +37,12 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"%%capture --no-stderr\n",
|
||||
"%pip install -U langchain langchain_openai langsmith pandas langchain_experimental matplotlib langgraph langchain_core"
|
||||
"%pip install -U langchain_community langchain_anthropic langchain_experimental matplotlib langgraph"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"execution_count": null,
|
||||
"id": "743c19df-6da9-4d1e-b2d2-ea40080b9fdc",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
@@ -56,7 +56,7 @@
|
||||
" os.environ[var] = getpass.getpass(f\"Please provide your {var}\")\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"_set_if_undefined(\"OPENAI_API_KEY\")\n",
|
||||
"_set_if_undefined(\"ANTHROPIC_API_KEY\")\n",
|
||||
"_set_if_undefined(\"TAVILY_API_KEY\")"
|
||||
]
|
||||
},
|
||||
@@ -73,57 +73,6 @@
|
||||
"</div>"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "5e4344a7-21df-4d54-90d2-9d19b3416ffb",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Create Agents\n",
|
||||
"\n",
|
||||
"The following helper functions will help create agents. These agents will then be nodes in the graph.\n",
|
||||
"\n",
|
||||
"You can skip ahead if you just want to see what the graph looks like."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 31,
|
||||
"id": "4325a10e-38dc-4a98-9004-e1525eaba377",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain_core.messages import (\n",
|
||||
" BaseMessage,\n",
|
||||
" HumanMessage,\n",
|
||||
" ToolMessage,\n",
|
||||
")\n",
|
||||
"from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder\n",
|
||||
"\n",
|
||||
"from langgraph.graph import END, StateGraph, START\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def create_agent(llm, tools, system_message: str):\n",
|
||||
" \"\"\"Create an agent.\"\"\"\n",
|
||||
" prompt = ChatPromptTemplate.from_messages(\n",
|
||||
" [\n",
|
||||
" (\n",
|
||||
" \"system\",\n",
|
||||
" \"You are a helpful AI assistant, collaborating with other assistants.\"\n",
|
||||
" \" Use the provided tools to progress towards answering the question.\"\n",
|
||||
" \" If you are unable to fully answer, that's OK, another assistant with different tools \"\n",
|
||||
" \" will help where you left off. Execute what you can to make progress.\"\n",
|
||||
" \" If you or any of the other assistants have the final answer or deliverable,\"\n",
|
||||
" \" prefix your response with FINAL ANSWER so the team knows to stop.\"\n",
|
||||
" \" You have access to the following tools: {tool_names}.\\n{system_message}\",\n",
|
||||
" ),\n",
|
||||
" MessagesPlaceholder(variable_name=\"messages\"),\n",
|
||||
" ]\n",
|
||||
" )\n",
|
||||
" prompt = prompt.partial(system_message=system_message)\n",
|
||||
" prompt = prompt.partial(tool_names=\", \".join([tool.name for tool in tools]))\n",
|
||||
" return prompt | llm.bind_tools(tools)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "b4b40de2-5dd4-4d5b-882e-577210723ff4",
|
||||
@@ -136,7 +85,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 63,
|
||||
"execution_count": 3,
|
||||
"id": "ca076f3b-a729-4ca9-8f91-05c2ba58d610",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
@@ -155,7 +104,7 @@
|
||||
"\n",
|
||||
"\n",
|
||||
"@tool\n",
|
||||
"def python_repl(\n",
|
||||
"def python_repl_tool(\n",
|
||||
" code: Annotated[str, \"The python code to execute to generate your chart.\"],\n",
|
||||
"):\n",
|
||||
" \"\"\"Use this to execute python code. If you want to see the output of a value,\n",
|
||||
@@ -180,37 +129,6 @@
|
||||
"Now that we've defined our tools and made some helper functions, will create the individual agents below and tell them how to talk to each other using LangGraph."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "0c6a8c3c-86a0-46aa-b970-ab070fb787d9",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Define State\n",
|
||||
"\n",
|
||||
"We first define the state of the graph. This will just a list of messages, along with a key to track the most recent sender"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 64,
|
||||
"id": "290c91d4-f6f4-443c-8181-233d39102974",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import operator\n",
|
||||
"from typing import Annotated, Sequence\n",
|
||||
"from typing_extensions import TypedDict\n",
|
||||
"\n",
|
||||
"from langchain_openai import ChatOpenAI\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# This defines the object that is passed between each node\n",
|
||||
"# in the graph. We will create different nodes for each agent and tool\n",
|
||||
"class AgentState(TypedDict):\n",
|
||||
" messages: Annotated[Sequence[BaseMessage], operator.add]\n",
|
||||
" sender: str"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "911a283e-ea04-40c1-b792-f9e5f7d81203",
|
||||
@@ -218,77 +136,91 @@
|
||||
"source": [
|
||||
"### Define Agent Nodes\n",
|
||||
"\n",
|
||||
"We now need to define the nodes. First, let's define the nodes for the agents."
|
||||
"We now need to define the nodes.\n",
|
||||
"\n",
|
||||
"First, we'll create a utility to create a system prompt for each agent."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 65,
|
||||
"execution_count": 4,
|
||||
"id": "4325a10e-38dc-4a98-9004-e1525eaba377",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def make_system_prompt(suffix: str) -> str:\n",
|
||||
" return (\n",
|
||||
" \"You are a helpful AI assistant, collaborating with other assistants.\"\n",
|
||||
" \" Use the provided tools to progress towards answering the question.\"\n",
|
||||
" \" If you are unable to fully answer, that's OK, another assistant with different tools \"\n",
|
||||
" \" will help where you left off. Execute what you can to make progress.\"\n",
|
||||
" \" If you or any of the other assistants have the final answer or deliverable,\"\n",
|
||||
" \" prefix your response with FINAL ANSWER so the team knows to stop.\"\n",
|
||||
" f\"\\n{suffix}\"\n",
|
||||
" )"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"id": "71b790ca-9cef-4b22-b469-4b1d5d8424d6",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import functools\n",
|
||||
"from langchain_core.messages import HumanMessage\n",
|
||||
"from langchain_anthropic import ChatAnthropic\n",
|
||||
"\n",
|
||||
"from langchain_core.messages import AIMessage\n",
|
||||
"from langgraph.prebuilt import create_react_agent\n",
|
||||
"from langgraph.graph import MessagesState\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Helper function to create a node for a given agent\n",
|
||||
"def agent_node(state, agent, name):\n",
|
||||
" result = agent.invoke(state)\n",
|
||||
" # We convert the agent output into a format that is suitable to append to the global state\n",
|
||||
" if isinstance(result, ToolMessage):\n",
|
||||
" pass\n",
|
||||
" else:\n",
|
||||
" result = AIMessage(**result.dict(exclude={\"type\", \"name\"}), name=name)\n",
|
||||
"llm = ChatAnthropic(model=\"claude-3-5-sonnet-latest\")\n",
|
||||
"\n",
|
||||
"# Research agent and node\n",
|
||||
"research_agent = create_react_agent(\n",
|
||||
" llm,\n",
|
||||
" tools=[tavily_tool],\n",
|
||||
" state_modifier=make_system_prompt(\n",
|
||||
" \"You can only do research. You are working with a chart generator colleague.\"\n",
|
||||
" ),\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def research_node(state: MessagesState) -> MessagesState:\n",
|
||||
" result = research_agent.invoke(state)\n",
|
||||
" # wrap in a human message, as not all providers allow\n",
|
||||
" # AI message at the last position of the input messages list\n",
|
||||
" result[\"messages\"][-1] = HumanMessage(\n",
|
||||
" content=result[\"messages\"][-1].content, name=\"researcher\"\n",
|
||||
" )\n",
|
||||
" return {\n",
|
||||
" \"messages\": [result],\n",
|
||||
" # Since we have a strict workflow, we can\n",
|
||||
" # track the sender so we know who to pass to next.\n",
|
||||
" \"sender\": name,\n",
|
||||
" # share internal message history of research agent with other agents\n",
|
||||
" \"messages\": result[\"messages\"],\n",
|
||||
" }\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"llm = ChatOpenAI(model=\"gpt-4o\")\n",
|
||||
"\n",
|
||||
"# Research agent and node\n",
|
||||
"research_agent = create_agent(\n",
|
||||
"# Chart generator agent and node\n",
|
||||
"# NOTE: THIS PERFORMS ARBITRARY CODE EXECUTION, WHICH CAN BE UNSAFE WHEN NOT SANDBOXED\n",
|
||||
"chart_agent = create_react_agent(\n",
|
||||
" llm,\n",
|
||||
" [tavily_tool],\n",
|
||||
" system_message=\"You should provide accurate data for the chart_generator to use.\",\n",
|
||||
" [python_repl_tool],\n",
|
||||
" state_modifier=make_system_prompt(\n",
|
||||
" \"You can only generate charts. You are working with a researcher colleague.\"\n",
|
||||
" ),\n",
|
||||
")\n",
|
||||
"research_node = functools.partial(agent_node, agent=research_agent, name=\"Researcher\")\n",
|
||||
"\n",
|
||||
"# chart_generator\n",
|
||||
"chart_agent = create_agent(\n",
|
||||
" llm,\n",
|
||||
" [python_repl],\n",
|
||||
" system_message=\"Any charts you display will be visible by the user.\",\n",
|
||||
")\n",
|
||||
"chart_node = functools.partial(agent_node, agent=chart_agent, name=\"chart_generator\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "71c7f1b2-24a3-4340-bcb2-feb22e344fb6",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Define Tool Node\n",
|
||||
"\n",
|
||||
"We now define a node to run the tools"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 66,
|
||||
"id": "d9a79c76-5c7c-42f6-91cf-635bc8305804",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langgraph.prebuilt import ToolNode\n",
|
||||
"\n",
|
||||
"tools = [tavily_tool, python_repl]\n",
|
||||
"tool_node = ToolNode(tools)"
|
||||
"def chart_node(state: MessagesState) -> MessagesState:\n",
|
||||
" result = chart_agent.invoke(state)\n",
|
||||
" # wrap in a human message, as not all providers allow\n",
|
||||
" # AI message at the last position of the input messages list\n",
|
||||
" result[\"messages\"][-1] = HumanMessage(\n",
|
||||
" content=result[\"messages\"][-1].content, name=\"chart_generator\"\n",
|
||||
" )\n",
|
||||
" return {\n",
|
||||
" # share internal message history of chart agent with other agents\n",
|
||||
" \"messages\": result[\"messages\"],\n",
|
||||
" }"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -303,22 +235,15 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 67,
|
||||
"id": "4f4b4d37-e8a3-4abb-8d42-eaea26016f35",
|
||||
"execution_count": 6,
|
||||
"id": "c30f800f-99dc-4207-ba09-0164e226dade",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Either agent can decide to end\n",
|
||||
"from typing import Literal\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def router(state):\n",
|
||||
"def router(state: MessagesState):\n",
|
||||
" # This is the router\n",
|
||||
" messages = state[\"messages\"]\n",
|
||||
" last_message = messages[-1]\n",
|
||||
" if last_message.tool_calls:\n",
|
||||
" # The previous agent is invoking a tool\n",
|
||||
" return \"call_tool\"\n",
|
||||
" if \"FINAL ANSWER\" in last_message.content:\n",
|
||||
" # Any agent decided the work is done\n",
|
||||
" return END\n",
|
||||
@@ -337,53 +262,41 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 68,
|
||||
"id": "4dce3901-6ad5-4df5-8528-6e865cf96cb0",
|
||||
"execution_count": 7,
|
||||
"id": "2c4a5ade-5912-494b-bf62-8a99278f9f12",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"workflow = StateGraph(AgentState)\n",
|
||||
"from langgraph.graph import StateGraph, START, END\n",
|
||||
"\n",
|
||||
"workflow.add_node(\"Researcher\", research_node)\n",
|
||||
"workflow = StateGraph(MessagesState)\n",
|
||||
"workflow.add_node(\"researcher\", research_node)\n",
|
||||
"workflow.add_node(\"chart_generator\", chart_node)\n",
|
||||
"workflow.add_node(\"call_tool\", tool_node)\n",
|
||||
"\n",
|
||||
"workflow.add_conditional_edges(\n",
|
||||
" \"Researcher\",\n",
|
||||
" \"researcher\",\n",
|
||||
" router,\n",
|
||||
" {\"continue\": \"chart_generator\", \"call_tool\": \"call_tool\", END: END},\n",
|
||||
" {\"continue\": \"chart_generator\", END: END},\n",
|
||||
")\n",
|
||||
"workflow.add_conditional_edges(\n",
|
||||
" \"chart_generator\",\n",
|
||||
" router,\n",
|
||||
" {\"continue\": \"Researcher\", \"call_tool\": \"call_tool\", END: END},\n",
|
||||
" {\"continue\": \"researcher\", END: END},\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"workflow.add_conditional_edges(\n",
|
||||
" \"call_tool\",\n",
|
||||
" # Each agent node updates the 'sender' field\n",
|
||||
" # the tool calling node does not, meaning\n",
|
||||
" # this edge will route back to the original agent\n",
|
||||
" # who invoked the tool\n",
|
||||
" lambda x: x[\"sender\"],\n",
|
||||
" {\n",
|
||||
" \"Researcher\": \"Researcher\",\n",
|
||||
" \"chart_generator\": \"chart_generator\",\n",
|
||||
" },\n",
|
||||
")\n",
|
||||
"workflow.add_edge(START, \"Researcher\")\n",
|
||||
"workflow.add_edge(START, \"researcher\")\n",
|
||||
"graph = workflow.compile()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 12,
|
||||
"execution_count": 8,
|
||||
"id": "97f8e0eb",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"image/jpeg": "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 truncated
|
||||
"image/png": "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 truncated
|
||||
"text/plain": [
|
||||
"<IPython.core.display.Image object>"
|
||||
]
|
||||
@@ -396,7 +309,7 @@
|
||||
"from IPython.display import Image, display\n",
|
||||
"\n",
|
||||
"try:\n",
|
||||
" display(Image(graph.get_graph(xray=True).draw_mermaid_png()))\n",
|
||||
" display(Image(graph.get_graph().draw_mermaid_png()))\n",
|
||||
"except Exception:\n",
|
||||
" # This requires some extra dependencies and is optional\n",
|
||||
" pass"
|
||||
@@ -414,73 +327,30 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 70,
|
||||
"id": "176a99b0-b457-45cf-8901-90facaa852da",
|
||||
"execution_count": 10,
|
||||
"id": "9f478b05-3f09-447f-a9f4-1b2eae73f5ef",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"{'Researcher': {'messages': [AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_3zDlnDMUkWEJxnHASo59doCL', 'function': {'arguments': '{\"query\":\"UK GDP 2018 to 2023\"}', 'name': 'tavily_search_results_json'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 26, 'prompt_tokens': 221, 'total_tokens': 247}, 'model_name': 'gpt-4o', 'system_fingerprint': None, 'finish_reason': 'tool_calls', 'logprobs': None}, name='Researcher', id='run-ac6640c6-2bb4-478f-b3c4-eabf98cf4900-0', tool_calls=[{'name': 'tavily_search_results_json', 'args': {'query': 'UK GDP 2018 to 2023'}, 'id': 'call_3zDlnDMUkWEJxnHASo59doCL'}])], 'sender': 'Researcher'}}\n",
|
||||
"----\n",
|
||||
"{'call_tool': {'messages': [ToolMessage(content='[{\"url\": \"https://www.ons.gov.uk/economy/grossdomesticproductgdp/timeseries/ihyp/pn2\", \"content\": \"Preliminary estimate of GDP time series (PGDP), released on 27 April 2018\\\\nPublications that use this data\\\\nContact details for this data\\\\nFooter links\\\\nHelp\\\\nAbout ONS\\\\nConnect with us\\\\nAll content is available under the Open Government Licence v3.0, except where otherwise stated Year on Year growth: CVM SA %\\\\nDownload full time series as:\\\\nDownload filtered time series as:\\\\nTable\\\\nNotes\\\\nFollowing a quality review it has been identified that the methodology used to estimate elements of purchased software within gross fixed capital formation (GFCF) has led to some double counting from 1997 onwards. GDP quarterly national accounts time series (QNA), released on 22 December 2023\\\\nIHYP: UK Economic Accounts time series (UKEA), released on 22 December 2023\\\\nIHYP: GDP first quarterly estimate time series\\\\n(PN2), released on 10 November 2023\\\\nIHYP: Year on Year growth: CVM SA %\\\\nSource dataset: GDP first quarterly estimate time series (PN2)\\\\nContact: Niamh McAuley\\\\nRelease date: 10 November 2023\\\\nView previous versions\\\\n %\\\\nFilters\\\\nCustom time period\\\\nChart\\\\nDownload this time seriesGross Domestic Product:\"}, {\"url\": \"https://www.ons.gov.uk/economy/grossdomesticproductgdp\", \"content\": \"Quarter on Quarter growth: CVM SA %\\\\nChained Volume Measures (CVM)\\\\nGross Domestic Product: q-on-q4 growth rate CVM SA %\\\\nChained Volume Measures (CVM)\\\\nGross Domestic Product at market prices: Current price: Seasonally adjusted \\\\u00a3m\\\\nCurrent Prices (CP)\\\\nGross Domestic Product: quarter on quarter growth rate: CP SA %\\\\nCurrent Prices (CP)\\\\nGross Domestic Product: q-on-q4 growth quarter growth: CP SA %\\\\nCurrent Prices (CP)\\\\nDatasets related to Gross Domestic Product (GDP)\\\\n A roundup of the latest data and trends on the economy, business and jobs\\\\nTime series related to Gross Domestic Product (GDP)\\\\nGross Domestic Product: chained volume measures: Seasonally adjusted \\\\u00a3m\\\\nChained Volume Measures (CVM)\\\\nGross Domestic Product: Hide\\\\nData and analysis from Census 2021\\\\nGross Domestic Product (GDP)\\\\nGross domestic product (GDP) estimates as the main measure of UK economic growth based on the value of goods and services produced during a given period. Contains current and constant price data on the value of goods and services to indicate the economic performance of the UK.\\\\nEstimates of short-term indicators of investment in non-financial assets; business investment and asset and sector breakdowns of total gross fixed capital formation.\\\\n Monthly gross domestic product by gross value added\\\\nThe gross value added (GVA) tables showing the monthly and annual growths and indices as published within the monthly gross domestic product (GDP) statistical bulletin.\\\\n\"}, {\"url\": \"https://www.macrotrends.net/global-metrics/countries/GBR/united-kingdom/gdp-gross-domestic-product\", \"content\": \"U.K. gdp for 2021 was $3,141.51B, a 16.45% increase from 2020. U.K. gdp for 2020 was $2,697.81B, a 5.39% decline from 2019. U.K. gdp for 2019 was $2,851.41B, a 0.69% decline from 2018. GDP at purchaser\\'s prices is the sum of gross value added by all resident producers in the economy plus any product taxes and minus any subsidies not included in ...\"}, {\"url\": \"https://www.statista.com/statistics/281744/gdp-of-the-united-kingdom/\", \"content\": \"Industry Overview\\\\nDigital & Trend reports\\\\nOverview and forecasts on trending topics\\\\nIndustry & Market reports\\\\nIndustry and market insights and forecasts\\\\nCompanies & Products reports\\\\nKey figures and rankings about companies and products\\\\nConsumer & Brand reports\\\\nConsumer and brand insights and preferences in various industries\\\\nPolitics & Society reports\\\\nDetailed information about political and social topics\\\\nCountry & Region reports\\\\nAll key figures about countries and regions\\\\nMarket forecast and expert KPIs for 1000+ markets in 190+ countries & territories\\\\nInsights on consumer attitudes and behavior worldwide\\\\nBusiness information on 100m+ public and private companies\\\\nExplore Company Insights\\\\nDetailed information for 39,000+ online stores and marketplaces\\\\nDirectly accessible data for 170 industries from 150+ countries\\\\nand over 1\\\\u00a0Mio. facts.\\\\n Transforming data into design:\\\\nStatista Content & Design\\\\nStrategy and business building for the data-driven economy:\\\\nGDP of the UK 1948-2022\\\\nUK economy expected to shrink in 2023\\\\nHow big is the UK economy compared to others?\\\\nGross domestic product of the United Kingdom from 1948 to 2022\\\\n(in million GBP)\\\\nAdditional Information\\\\nShow sources information\\\\nShow publisher information\\\\nUse Ask Statista Research Service\\\\nDecembLine truncated
|
||||
"----\n",
|
||||
"{'Researcher': {'messages': [AIMessage(content=\"The search results provide some information about the UK's GDP over the past years, but most of the relevant data is either not in a structured format that can be easily extracted or it is behind a source that requires further access for detailed statistics. To proceed with generating a line graph, we need specific GDP values for each year from 2018 to 2023.\\n\\nHowever, one of the search results from macrotrends.net does provide specific GDP values for the years 2018 to 2021:\\n\\n- U.K. GDP for 2021 was $3,141.51 billion, a 16.45% increase from 2020.\\n- U.K. GDP for 2020 was $2,697.81 billion, a 5.39% decline from 2019.\\n- U.K. GDP for 2019 was $2,851.41 billion, a 0.69% decline from 2018.\\n\\nWe still need the GDP values for 2022 and 2023 to complete the dataset for the past five years. I will now conduct a further search to find the missing GDP data for 2022 and 2023.\", additional_kwargs={'tool_calls': [{'id': 'call_nvB1wQyQuNeTrOXQZnEtgNDZ', 'function': {'arguments': '{\"query\":\"UK GDP 2022 2023\"}', 'name': 'tavily_search_results_json'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 263, 'prompt_tokens': 3199, 'total_tokens': 3462}, 'model_name': 'gpt-4o', 'system_fingerprint': None, 'finish_reason': 'tool_calls', 'logprobs': None}, name='Researcher', id='run-25901401-0d62-485f-b7d5-37e3c159effe-0', tool_calls=[{'name': 'tavily_search_results_json', 'args': {'query': 'UK GDP 2022 2023'}, 'id': 'call_nvB1wQyQuNeTrOXQZnEtgNDZ'}])], 'sender': 'Researcher'}}\n",
|
||||
"----\n",
|
||||
"{'call_tool': {'messages': [ToolMessage(content='[{\"url\": \"https://www.statista.com/statistics/281744/gdp-of-the-united-kingdom/\", \"content\": \"Industry Overview\\\\nDigital & Trend reports\\\\nOverview and forecasts on trending topics\\\\nIndustry & Market reports\\\\nIndustry and market insights and forecasts\\\\nCompanies & Products reports\\\\nKey figures and rankings about companies and products\\\\nConsumer & Brand reports\\\\nConsumer and brand insights and preferences in various industries\\\\nPolitics & Society reports\\\\nDetailed information about political and social topics\\\\nCountry & Region reports\\\\nAll key figures about countries and regions\\\\nMarket forecast and expert KPIs for 1000+ markets in 190+ countries & territories\\\\nInsights on consumer attitudes and behavior worldwide\\\\nBusiness information on 100m+ public and private companies\\\\nExplore Company Insights\\\\nDetailed information for 39,000+ online stores and marketplaces\\\\nDirectly accessible data for 170 industries from 150+ countries\\\\nand over 1\\\\u00a0Mio. facts.\\\\n Transforming data into design:\\\\nStatista Content & Design\\\\nStrategy and business building for the data-driven economy:\\\\nGDP of the UK 1948-2022\\\\nUK economy expected to shrink in 2023\\\\nHow big is the UK economy compared to others?\\\\nGross domestic product of the United Kingdom from 1948 to 2022\\\\n(in million GBP)\\\\nAdditional Information\\\\nShow sources information\\\\nShow publisher information\\\\nUse Ask Statista Research Service\\\\nDecember 2023\\\\nUnited Kingdom\\\\n1948 to 2022\\\\n*GDP is displayed in real terms (seasonally adjusted chained volume measure with 2019 as the reference year)\\\\n Statistics on\\\\n\\\\\"\\\\nEconomy of the UK\\\\n\\\\\"\\\\nOther statistics that may interest you Economy of the UK\\\\nGross domestic product\\\\nLabor Market\\\\nInflation\\\\nGovernment finances\\\\nBusiness Enterprise\\\\nFurther related statistics\\\\nFurther Content: You might find this interesting as well\\\\nStatistics\\\\nTopics Other statistics on the topicThe UK economy\\\\nEconomy\\\\nRPI annual inflation rate UK 2000-2028\\\\nEconomy\\\\nCPI annual inflation rate UK 2000-2028\\\\nEconomy\\\\nAverage annual earnings for full-time employees in the UK 1999-2023\\\\nEconomy\\\\nInflation rate in the UK 1989-2023\\\\nYou only have access to basic statistics.\\\\n Customized Research & Analysis projects:\\\\nGet quick analyses with our professional research service\\\\nThe best of the best: the portal for top lists & rankings:\\\\n\"}, {\"url\": \"https://www.ons.gov.uk/economy/grossdomesticproductgdp\", \"content\": \"Quarter on Quarter growth: CVM SA %\\\\nChained Volume Measures (CVM)\\\\nGross Domestic Product: q-on-q4 growth rate CVM SA %\\\\nChained Volume Measures (CVM)\\\\nGross Domestic Product at market prices: Current price: Seasonally adjusted \\\\u00a3m\\\\nCurrent Prices (CP)\\\\nGross Domestic Product: quarter on quarter growth rate: CP SA %\\\\nCurrent Prices (CP)\\\\nGross Domestic Product: q-on-q4 growth quarter growth: CP SA %\\\\nCurrent Prices (CP)\\\\nDatasets related to Gross Domestic Product (GDP)\\\\n A roundup of the latest data and trends on the economy, business and jobs\\\\nTime series related to Gross Domestic Product (GDP)\\\\nGross Domestic Product: chained volume measures: Seasonally adjusted \\\\u00a3m\\\\nChained Volume Measures (CVM)\\\\nGross Domestic Product: Hide\\\\nData and analysis from Census 2021\\\\nGross Domestic Product (GDP)\\\\nGross domestic product (GDP) estimates as the main measure of UK economic growth based on the value of goods and services produced during a given period. Contains current and constant price data on the value of goods and services to indicate the economic performance of the UK.\\\\nEstimates of short-term indicators of investment in non-financial assets; business investment and asset and sector breakdowns of total gross fixed capital formation.\\\\n Monthly gross domestic product by gross value added\\\\nThe gross value added (GVA) tables showing the monthly and annual growths and indices as published within the monthly gross domestic product (GDP) statistical bulletin.\\\\n\"}, {\"url\": \"https://www.ons.gov.uk/economy/grossdomesticproductgdp/bulletins/gdpfirstquarterlyestimateuk/octobertodecember2023\", \"content\": \"This review covered:\\\\nprocesses and quality assurance in making revisions to GDP\\\\npotential improvements to early estimates of GDP enabled through enhanced access to data\\\\ncommunication of revisions to GDP, the story behind the most recent set of revisions in particular, and uncertainty in early estimates of GDP\\\\nWe have already started work looking into the recommendations of this review and have set out our plans on how we will improve the way we communicate uncertainty.\\\\n Source: GDP first quarterly estimate from the Office for National Statistics\\\\nNotes\\\\nOffice for Statistics Regulation RevisiLine truncated
|
||||
"----\n",
|
||||
"{'Researcher': {'messages': [AIMessage(content=\"The search results did not provide exact figures for the UK's GDP in 2022 and 2023. While there are several references to GDP data, growth rates, and quarterly figures, we do not have the specific annual GDP values in a consistent currency format (such as USD or GBP) that would allow us to compile a complete dataset for the past five years.\\n\\nTo proceed, we will need to find another source or use a different method to obtain the missing GDP data for 2022 and 2023. If this data is not available, we may not be able to draw an accurate line graph of the UK's GDP over the past five years.\", response_metadata={'token_usage': {'completion_tokens': 134, 'prompt_tokens': 6996, 'total_tokens': 7130}, 'model_name': 'gpt-4o', 'system_fingerprint': None, 'finish_reason': 'stop', 'logprobs': None}, name='Researcher', id='run-aa7d307d-cfdd-4c83-ad09-b6b0efbffe6e-0')], 'sender': 'Researcher'}}\n",
|
||||
"----\n",
|
||||
"{'chart_generator': {'messages': [AIMessage(content=\"It seems we have hit a roadblock in finding the exact GDP figures for the UK for the years 2022 and 2023. The information provided by the search results does not include the specific data we need. Therefore, we currently do not have the complete dataset to generate a line graph of the UK's GDP over the past five years.\\n\\nTo proceed, we might need to look for an official statistical release or a comprehensive economic report that includes the GDP figures for 2022 and 2023. If such data can be obtained, we can then use it to create the desired line graph. Without this data, we cannot fulfill the request as specified.\", response_metadata={'token_usage': {'completion_tokens': 134, 'prompt_tokens': 7150, 'total_tokens': 7284}, 'model_name': 'gpt-4o', 'system_fingerprint': None, 'finish_reason': 'stop', 'logprobs': None}, name='chart_generator', id='run-a667e647-45b2-414e-b301-81f846fa59ad-0')], 'sender': 'chart_generator'}}\n",
|
||||
"----\n",
|
||||
"{'Researcher': {'messages': [AIMessage(content=\"I am unable to proceed with generating the line graph as we lack the specific GDP data for the UK for the years 2022 and 2023. To complete this task, we would need to acquire the missing data points. If the data becomes available, or if there's an alternative source that can provide the figures, we can revisit this task and generate the line graph accordingly. For now, we must conclude this attempt.\", response_metadata={'token_usage': {'completion_tokens': 87, 'prompt_tokens': 7276, 'total_tokens': 7363}, 'model_name': 'gpt-4o', 'system_fingerprint': None, 'finish_reason': 'stop', 'logprobs': None}, name='Researcher', id='run-1731ab9f-7ee9-4ff9-a920-7b998a41fe4d-0')], 'sender': 'Researcher'}}\n",
|
||||
"----\n",
|
||||
"{'chart_generator': {'messages': [AIMessage(content=\"As of my last attempt, I have not been able to find the exact GDP figures for the UK for the years 2022 and 2023. Without these figures, it's not possible to generate a complete and accurate line graph of the UK's GDP over the past five years. Should the data become available or if an alternative source can provide the figures, we can proceed with creating the graph. For the time being, this task cannot be completed.\", response_metadata={'token_usage': {'completion_tokens': 93, 'prompt_tokens': 7383, 'total_tokens': 7476}, 'model_name': 'gpt-4o', 'system_fingerprint': None, 'finish_reason': 'stop', 'logprobs': None}, name='chart_generator', id='run-6e12dccd-b1b2-4c7d-befd-4a338d027756-0')], 'sender': 'chart_generator'}}\n",
|
||||
"----\n",
|
||||
"{'Researcher': {'messages': [AIMessage(content=\"I have attempted to gather the UK's GDP data for the past five years to draw a line graph, but I was only able to obtain confirmed figures for the years 2018 through 2021. The GDP data for 2022 and 2023 was not available in the search results. Without complete data, it is not possible to generate the line graph as requested. If the missing data for 2022 and 2023 becomes available or an alternative verified source can provide these figures, we can revisit this task to create the graph.\", response_metadata={'token_usage': {'completion_tokens': 111, 'prompt_tokens': 7468, 'total_tokens': 7579}, 'model_name': 'gpt-4o', 'system_fingerprint': None, 'finish_reason': 'stop', 'logprobs': None}, name='Researcher', id='run-9d93e637-cedd-4b17-945a-cc97e490432c-0')], 'sender': 'Researcher'}}\n",
|
||||
"----\n",
|
||||
"{'chart_generator': {'messages': [AIMessage(content=\"I've exhausted the available tools to find the UK's GDP for the past five years, but I was able to obtain data up to the year 2021. Unfortunately, without the GDP data for 2022 and 2023, I cannot create a complete line graph for the UK's GDP over the past five years.\\n\\nTo generate the line graph, we would need to have all the necessary data points for each year from 2018 to 2023. If you can provide the GDP data for 2022 and 2023 or direct me to a specific source where these figures can be found, I will be able to create the line graph for you.\", response_metadata={'token_usage': {'completion_tokens': 136, 'prompt_tokens': 7599, 'total_tokens': 7735}, 'model_name': 'gpt-4o', 'system_fingerprint': None, 'finish_reason': 'stop', 'logprobs': None}, name='chart_generator', id='run-82063d76-6655-473e-9f6a-a5c005d92bd3-0')], 'sender': 'chart_generator'}}\n",
|
||||
"----\n",
|
||||
"{'Researcher': {'messages': [AIMessage(content=\"I have exhausted the available tools to find the UK's GDP for the past five years, but I was able to obtain data up to the year 2021. Unfortunately, without the GDP data for 2022 and 2023, I cannot create a complete line graph for the UK's GDP over the past five years.\\n\\nTo generate the line graph, we would need to have all the necessary data points for each year from 2018 to 2023. If you can provide the GDP data for 2022 and 2023 or direct me to a specific source where these figures can be found, I will be able to create the line graph for you.\", response_metadata={'token_usage': {'completion_tokens': 136, 'prompt_tokens': 7727, 'total_tokens': 7863}, 'model_name': 'gpt-4o', 'system_fingerprint': 'fp_85bf4c41a2', 'finish_reason': 'stop', 'logprobs': None}, name='Researcher', id='run-954e7bb2-ae3e-4610-9927-4b16d733414e-0')], 'sender': 'Researcher'}}\n",
|
||||
"----\n",
|
||||
"{'chart_generator': {'messages': [AIMessage(content=\"Having attempted to find the UK's GDP figures for the years 2022 and 2023 through available data sources and coming up short, it appears we are currently unable to produce a complete line graph of the UK's GDP over the past five years as initially requested. To create a graph, we would need precise GDP data for each year from 2018 to 2023.\\n\\nIf the missing GDP data for 2022 and 2023 becomes available or if an alternative source can provide these figures, we can then proceed to generate the line graph. As of now, this task must be paused until the necessary data can be obtained.\", response_metadata={'token_usage': {'completion_tokens': 130, 'prompt_tokens': 7883, 'total_tokens': 8013}, 'model_name': 'gpt-4o', 'system_fingerprint': 'fp_85bf4c41a2', 'finish_reason': 'stop', 'logprobs': None}, name='chart_generator', id='run-8d1382e2-a77c-4d2f-b06a-2597be59542b-0')], 'sender': 'chart_generator'}}\n",
|
||||
"----\n",
|
||||
"{'Researcher': {'messages': [AIMessage(content=\"The search results do not provide the exact GDP figures for the UK for 2022 and 2023. Without this information, it is not possible to generate a line graph of the UK's GDP over the past five years. We would require the GDP values for those two years to complete the dataset and create the graph. As of now, I must conclude this task until the necessary data becomes available.\", response_metadata={'token_usage': {'completion_tokens': 82, 'prompt_tokens': 8005, 'total_tokens': 8087}, 'model_name': 'gpt-4o', 'system_fingerprint': None, 'finish_reason': 'stop', 'logprobs': None}, name='Researcher', id='run-246b9b29-ffc7-4da9-a09a-0dcfbbb3bd7a-0')], 'sender': 'Researcher'}}\n",
|
||||
"----\n",
|
||||
"{'chart_generator': {'messages': [AIMessage(content=\"I have attempted to find the UK's GDP for the past five years to create a line graph, but I could only obtain confirmed figures for the years 2018 through 2021. The GDP data for 2022 and 2023 was not available in the search results. Without complete data, it is not possible to generate the line graph as requested. If the missing data for 2022 and 2023 becomes available or an alternative verified source can provide these figures, we can revisit this task to create the graph.\", response_metadata={'token_usage': {'completion_tokens': 108, 'prompt_tokens': 8107, 'total_tokens': 8215}, 'model_name': 'gpt-4o', 'system_fingerprint': 'fp_85bf4c41a2', 'finish_reason': 'stop', 'logprobs': None}, name='chart_generator', id='run-f2847a80-610d-49c5-924a-ccffccb7cd5a-0')], 'sender': 'chart_generator'}}\n",
|
||||
"----\n",
|
||||
"{'Researcher': {'messages': [AIMessage(content=\"As of now, I was unable to obtain the complete data for the UK's GDP over the past five years due to lack of specific information for the years 2022 and 2023. Therefore, it's not possible to draw a line graph of the UK's GDP for this period without the complete dataset. Further action to acquire the missing data would be required to proceed.\", response_metadata={'token_usage': {'completion_tokens': 77, 'prompt_tokens': 8207, 'total_tokens': 8284}, 'model_name': 'gpt-4o', 'system_fingerprint': None, 'finish_reason': 'stop', 'logprobs': None}, name='Researcher', id='run-28e09000-8787-4ac0-a7d8-0aba888c2520-0')], 'sender': 'Researcher'}}\n",
|
||||
"----\n",
|
||||
"{'chart_generator': {'messages': [AIMessage(content=\"It appears we have encountered a limitation in obtaining the complete GDP data for the UK for 2022 and 2023. Without these figures, we cannot create the line graph of the UK's GDP over the past five years as requested. If the data becomes available, or if there's an alternative source that can provide the figures, we can revisit this task and generate the line graph accordingly. For now, this task will have to be concluded without completion.\", response_metadata={'token_usage': {'completion_tokens': 93, 'prompt_tokens': 8304, 'total_tokens': 8397}, 'model_name': 'gpt-4o', 'system_fingerprint': None, 'finish_reason': 'stop', 'logprobs': None}, name='chart_generator', id='run-8bf8f247-cb86-4ef0-a81b-14da2d27b6f1-0')], 'sender': 'chart_generator'}}\n",
|
||||
"----\n",
|
||||
"{'Researcher': {'messages': [AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_df3UdS3vJkJFB30O0WYq38k8', 'function': {'arguments': '{\"query\":\"UK GDP 2022 2023 statistics\"}', 'name': 'tavily_search_results_json'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 26, 'prompt_tokens': 8389, 'total_tokens': 8415}, 'model_name': 'gpt-4o', 'system_fingerprint': None, 'finish_reason': 'tool_calls', 'logprobs': None}, name='Researcher', id='run-e1577cc7-5673-4821-9683-34947c7a2bc5-0', tool_calls=[{'name': 'tavily_search_results_json', 'args': {'query': 'UK GDP 2022 2023 statistics'}, 'id': 'call_df3UdS3vJkJFB30O0WYq38k8'}])], 'sender': 'Researcher'}}\n",
|
||||
"----\n",
|
||||
"{'call_tool': {'messages': [ToolMessage(content='[{\"url\": \"https://www.statista.com/statistics/281744/gdp-of-the-united-kingdom/\", \"content\": \"Industry Overview\\\\nDigital & Trend reports\\\\nOverview and forecasts on trending topics\\\\nIndustry & Market reports\\\\nIndustry and market insights and forecasts\\\\nCompanies & Products reports\\\\nKey figures and rankings about companies and products\\\\nConsumer & Brand reports\\\\nConsumer and brand insights and preferences in various industries\\\\nPolitics & Society reports\\\\nDetailed information about political and social topics\\\\nCountry & Region reports\\\\nAll key figures about countries and regions\\\\nMarket forecast and expert KPIs for 1000+ markets in 190+ countries & territories\\\\nInsights on consumer attitudes and behavior worldwide\\\\nBusiness information on 100m+ public and private companies\\\\nExplore Company Insights\\\\nDetailed information for 39,000+ online stores and marketplaces\\\\nDirectly accessible data for 170 industries from 150+ countries\\\\nand over 1\\\\u00a0Mio. facts.\\\\n Transforming data into design:\\\\nStatista Content & Design\\\\nStrategy and business building for the data-driven economy:\\\\nGDP of the UK 1948-2022\\\\nUK economy expected to shrink in 2023\\\\nHow big is the UK economy compared to others?\\\\nGross domestic product of the United Kingdom from 1948 to 2022\\\\n(in million GBP)\\\\nAdditional Information\\\\nShow sources information\\\\nShow publisher information\\\\nUse Ask Statista Research Service\\\\nDecember 2023\\\\nUnited Kingdom\\\\n1948 to 2022\\\\n*GDP is displayed in real terms (seasonally adjusted chained volume measure with 2019 as the reference year)\\\\n Statistics on\\\\n\\\\\"\\\\nEconomy of the UK\\\\n\\\\\"\\\\nOther statistics that may interest you Economy of the UK\\\\nGross domestic product\\\\nLabor Market\\\\nInflation\\\\nGovernment finances\\\\nBusiness Enterprise\\\\nFurther related statistics\\\\nFurther Content: You might find this interesting as well\\\\nStatistics\\\\nTopics Other statistics on the topicThe UK economy\\\\nEconomy\\\\nRPI annual inflation rate UK 2000-2028\\\\nEconomy\\\\nCPI annual inflation rate UK 2000-2028\\\\nEconomy\\\\nAverage annual earnings for full-time employees in the UK 1999-2023\\\\nEconomy\\\\nInflation rate in the UK 1989-2023\\\\nYou only have access to basic statistics.\\\\n Customized Research & Analysis projects:\\\\nGet quick analyses with our professional research service\\\\nThe best of the best: the portal for top lists & rankings:\\\\n\"}, {\"url\": \"https://www.statista.com/topics/3795/gdp-of-the-uk/\", \"content\": \"Monthly growth of gross domestic product in the United Kingdom from January 2019 to November 2023\\\\nContribution to GDP growth in the UK 2023, by sector\\\\nContribution to gross domestic product growth in the United Kingdom in January 2023, by sector\\\\nGDP growth rate in the UK 1999-2021, by country\\\\nAnnual growth rates of gross domestic product in the United Kingdom from 1999 to 2021, by country\\\\nGDP growth rate in the UK 2021, by region\\\\nAnnual growth rates of gross domestic product in the United Kingdom in 2021, by region\\\\nGDP growth of Scotland 2021, by local area\\\\nAnnual growth rates of gross domestic product in Scotland in 2021, by local (ITL 3) area\\\\nGDP growth of Wales 2021, by local area\\\\nAnnual growth rates of gross domestic product in Wales in 2021, by local (ITL 3) area\\\\nGDP growth of Northern Ireland 2021, by local area\\\\nAnnual growth rates of gross domestic product in Northern Ireland in 2021, by local (ITL 3) area\\\\nGDP per capita\\\\nGDP per capita\\\\nGDP per capita in the UK 1955-2022\\\\nGross domestic product per capita in the United Kingdom from 1955 to 2022 (in GBP)\\\\nAnnual GDP per capita growth in the UK 1956-2022\\\\nAnnual GDP per capita growth in the United Kingdom from 1956 to 2022\\\\nQuarterly GDP per capita in the UK 2019-2023\\\\nQuarterly GDP per capita in the United Kingdom from 1st quarter 2019 to 3rd quarter 2023 (in GBP)\\\\nQuarterly GDP per capita growth in the UK 2019-2023\\\\nQuarterly GDP per capita growth in the United Kingdom from 1st quarter 2019 to 3rd quarter 2023 (in GBP)\\\\nGDP per capita of the UK 1999-2021, by country\\\\nGross domestic product per capita of the United Kingdom from 1999 to 2021, by country (in GBP)\\\\nGDP per capita of the UK 2021, by region\\\\nGross domestic product per capita of the United Kingdom in 2021, by region (in GBP)\\\\nGlobal Comparisons\\\\nGlobal Comparisons\\\\nCountries with the largest gross domestic product (GDP) 2022\\\\n Monthly GDP of the UK 2019-2023\\\\nMonthly index of gross domestic product in the United Kingdom from January 2019 to November 2023 (2019=100)\\\\nGVA of the UK 2022, by sector\\\\nGross value added of the United Kingdom in 2022, by industry sector (in million GBP)\\\\nGDP of the UK 2021, by country\\\\nGross domestic product of the United Kingdom in Line truncated
|
||||
"----\n",
|
||||
"{'Researcher': {'messages': [AIMessage(content=\"The search results unfortunately do not provide specific figures for the UK's GDP in 2022 and 2023. While there are references to GDP data and related economic indicators, the exact annual GDP values needed to create the line graph are not present.\\n\\nOne possible approach to obtain these figures would be to access detailed statistical databases or reports from official sources such as the Office for National Statistics (ONS) or economic research institutions that publish historical GDP data. These sources might have the most recent and accurate GDP figures available for the UK, which are necessary to complete the line graph.\\n\\nSince I cannot directly access or retrieve the data from these sources using the tools available to me, I recommend consulting such databases or reports to find the UK's GDP for 2022 and 2023. Once the data is obtained, it can be used to create the line graph.\", response_metadata={'token_usage': {'completion_tokens': 172, 'prompt_tokens': 12099, 'total_tokens': 12271}, 'model_name': 'gpt-4o', 'system_fingerprint': None, 'finish_reason': 'stop', 'logprobs': None}, name='Researcher', id='run-7b4bcbc3-3ed0-4fa0-8e5d-a366c5a80d5a-0')], 'sender': 'Researcher'}}\n",
|
||||
"----\n",
|
||||
"{'chart_generator': {'messages': [AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_JPVxDAzEFi21crVT7Rt6SRJl', 'function': {'arguments': '{\"code\":\"import matplotlib.pyplot as plt\\\\n\\\\n# UK GDP data obtained from search results for 2018-2021 (in billion USD)\\\\n# The data for 2022 and 2023 are placeholders and should be replaced with actual figures when available\\\\ngdp_data = {\\\\n \\'Year\\': [2018, 2019, 2020, 2021, 2022, 2023],\\\\n \\'GDP (Billion USD)\\': [2851.41, 2697.81, 3141.51, None, None]\\\\n}\\\\n\\\\n# Plot a line graph\\\\nplt.figure(figsize=(10, 5))\\\\nplt.plot(gdp_data[\\'Year\\'], gdp_data[\\'GDP (Billion USD)\\'], marker=\\'o\\')\\\\n\\\\n# Title and labels\\\\nplt.title(\\'UK GDP from 2018 to 2023\\')\\\\nplt.xlabel(\\'Year\\')\\\\nplt.ylabel(\\'GDP (Billion USD)\\')\\\\n\\\\n# Show grid\\\\nplt.grid(True)\\\\n\\\\n# Display the graph\\\\nplt.show()\"}', 'name': 'python_repl'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 240, 'prompt_tokens': 12291, 'total_tokens': 12531}, 'model_name': 'gpt-4o', 'system_fingerprint': None, 'finish_reason': 'tool_calls', 'logprobs': None}, name='chart_generator', id='run-6cff57bc-ba87-4690-9528-4d15bba7986c-0', tool_calls=[{'name': 'python_repl', 'args': {'code': \"import matplotlib.pyplot as plt\\n\\n# UK GDP data obtained from search results for 2018-2021 (in billion USD)\\n# The data for 2022 and 2023 are placeholders and should be replaced with actual figures when available\\ngdp_data = {\\n 'Year': [2018, 2019, 2020, 2021, 2022, 2023],\\n 'GDP (Billion USD)': [2851.41, 2697.81, 3141.51, None, None]\\n}\\n\\n# Plot a line graph\\nplt.figure(figsize=(10, 5))\\nplt.plot(gdp_data['Year'], gdp_data['GDP (Billion USD)'], marker='o')\\n\\n# Title and labels\\nplt.title('UK GDP from 2018 to 2023')\\nplt.xlabel('Year')\\nplt.ylabel('GDP (Billion USD)')\\n\\n# Show grid\\nplt.grid(True)\\n\\n# Display the graph\\nplt.show()\"}, 'id': 'call_JPVxDAzEFi21crVT7Rt6SRJl'}])], 'sender': 'chart_generator'}}\n",
|
||||
"----\n",
|
||||
"{'call_tool': {'messages': [ToolMessage(content=\"Successfully executed:\\n```python\\nimport matplotlib.pyplot as plt\\n\\n# UK GDP data obtained from search results for 2018-2021 (in billion USD)\\n# The data for 2022 and 2023 are placeholders and should be replaced with actual figures when available\\ngdp_data = {\\n 'Year': [2018, 2019, 2020, 2021, 2022, 2023],\\n 'GDP (Billion USD)': [2851.41, 2697.81, 3141.51, None, None]\\n}\\n\\n# Plot a line graph\\nplt.figure(figsize=(10, 5))\\nplt.plot(gdp_data['Year'], gdp_data['GDP (Billion USD)'], marker='o')\\n\\n# Title and labels\\nplt.title('UK GDP from 2018 to 2023')\\nplt.xlabel('Year')\\nplt.ylabel('GDP (Billion USD)')\\n\\n# Show grid\\nplt.grid(True)\\n\\n# Display the graph\\nplt.show()\\n```\\nStdout: ValueError('x and y must have same first dimension, but have shapes (6,) and (5,)')\\n\\nIf you have completed all tasks, respond with FINAL ANSWER.\", name='python_repl', tool_call_id='call_JPVxDAzEFi21crVT7Rt6SRJl')]}}\n",
|
||||
"{'researcher': {'messages': [HumanMessage(content=\"First, get the UK's GDP over the past 5 years, then make a line chart of it. Once you make the chart, finish.\", additional_kwargs={}, response_metadata={}, id='fa1f5e95-9e1a-47d4-b4b6-e93f345e339d'), AIMessage(content=[{'text': \"I'll help search for the UK's GDP data over the past 5 years. Then my colleague can help create the line chart.\", 'type': 'text'}, {'id': 'toolu_01Jd9dxa4Ss2NhzBhCuwUX3E', 'input': {'query': 'UK GDP annual data past 5 years 2019-2023'}, 'name': 'tavily_search_results_json', 'type': 'tool_use'}], additional_kwargs={}, response_metadata={'id': 'msg_014nCkfVHnG6LAsiS6pY7zcd', 'model': 'claude-3-5-sonnet-20241022', 'stop_reason': 'tool_use', 'stop_sequence': None, 'usage': {'input_tokens': 555, 'output_tokens': 101}}, id='run-e2297529-9972-4de6-835d-23d920b0e29b-0', tool_calls=[{'name': 'tavily_search_results_json', 'args': {'query': 'UK GDP annual data past 5 years 2019-2023'}, 'id': 'toolu_01Jd9dxa4Ss2NhzBhCuwUX3E', 'type': 'tool_call'}], usage_metadata={'input_tokens': 555, 'output_tokens': 101, 'total_tokens': 656, 'input_token_details': {}}), ToolMessage(content='[{\"url\": \"https://www.macrotrends.net/global-metrics/countries/GBR/united-kingdom/gdp-gross-domestic-product\", \"content\": \"Dollar figures for GDP are converted from domestic currencies using single year official exchange rates. For a few countries where the official exchange rate does not reflect the rate effectively applied to actual foreign exchange transactions, an alternative conversion factor is used. U.K. gdp for 2023 was $3,340.03B, a 8.13% increase from 2022.\"}, {\"url\": \"https://www.statista.com/topics/3795/gdp-of-the-uk/\", \"content\": \"Monthly growth of gross domestic product in the United Kingdom from January 2019 to November 2023\\\\nContribution to GDP growth in the UK 2023, by sector\\\\nContribution to gross domestic product growth in the United Kingdom in January 2023, by sector\\\\nGDP growth rate in the UK 1999-2021, by country\\\\nAnnual growth rates of gross domestic product in the United Kingdom from 1999 to 2021, by country\\\\nGDP growth rate in the UK 2021, by region\\\\nAnnual growth rates of gross domestic product in the United Kingdom in 2021, by region\\\\nGDP growth of Scotland 2021, by local area\\\\nAnnual growth rates of gross domestic product in Scotland in 2021, by local (ITL 3) area\\\\nGDP growth of Wales 2021, by local area\\\\nAnnual growth rates of gross domestic product in Wales in 2021, by local (ITL 3) area\\\\nGDP growth of Northern Ireland 2021, by local area\\\\nAnnual growth rates of gross domestic product in Northern Ireland in 2021, by local (ITL 3) area\\\\nGDP per capita\\\\nGDP per capita\\\\nGDP per capita in the UK 1955-2022\\\\nGross domestic product per capita in the United Kingdom from 1955 to 2022 (in GBP)\\\\nAnnual GDP per capita growth in the UK 1956-2022\\\\nAnnual GDP per capita growth in the United Kingdom from 1956 to 2022\\\\nQuarterly GDP per capita in the UK 2019-2023\\\\nQuarterly GDP per capita in the United Kingdom from 1st quarter 2019 to 3rd quarter 2023 (in GBP)\\\\nQuarterly GDP per capita growth in the UK 2019-2023\\\\nQuarterly GDP per capita growth in the United Kingdom from 1st quarter 2019 to 3rd quarter 2023 (in GBP)\\\\nGDP per capita of the UK 1999-2021, by country\\\\nGross domestic product per capita of the United Kingdom from 1999 to 2021, by country (in GBP)\\\\nGDP per capita of the UK 2021, by region\\\\nGross domestic product per capita of the United Kingdom in 2021, by region (in GBP)\\\\nGlobal Comparisons\\\\nGlobal Comparisons\\\\nCountries with the largest gross domestic product (GDP) 2022\\\\n Monthly GDP of the UK 2019-2023\\\\nMonthly index of gross domestic product in the United Kingdom from January 2019 to November 2023 (2019=100)\\\\nGVA of the UK 2022, by sector\\\\nGross value added of the United Kingdom in 2022, by industry sector (in million GBP)\\\\nGDP of the UK 2021, by country\\\\nGross domestic product of the United Kingdom in 2021, by country (in million GBP)\\\\nGDP of the UK 2021, by region\\\\nGross domestic product of the United Kingdom in 2021, by region (in million GBP)\\\\nGDP of Scotland 2021, by local area\\\\nGross domestic product of Scotland in 2021, by local (ITL 3) area (in million GBP)\\\\nGDP of Wales 2021, by local area\\\\nGross domestic product of Wales in 2021, by local (ITL 3) area (in million GBP)\\\\nGDP of Northern Ireland 2021, by local area\\\\nGross domestic product of Northern Ireland in 2021, by local (ITL 3) area (in million GBP)\\\\nGDP growth\\\\nGDP growth\\\\nGDP growth forecast for the UK 2000-2028\\\\nForecasted annual growth of gross domestic product in the United Kingdom from 2000 to 2028\\\\nAnnual GDP growth in the UK 1949-2022\\\\nAnnual growth of gross domestic product in the United Kingdom from 1949 to 2022\\\\nQuarterly GDP growth of the UK 2019-2023\\\\nQuarterly growth of gross domestic product Line truncated
|
||||
"----\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"image/png": "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3bF48eKIiFi4cGFMmzYtGhsbIyJi7ty5cd9998X5558fVVVV8frrr8edd94Zc+fO7Y8jAACAsZJ3FNXV1cW+ffti+fLl0d7eHjNnzozm5ub+D1/YvXv3gDtDd9xxRxQUFMQdd9wRb731VnziE5+IuXPnxk9+8pPhexUAAABDVJCNg/ewdXV1RVlZWXR2dkZpaelYbwcAABgjI9EGI/7pcwAAAB9loggAAEiaKAIAAJImigAAgKSJIgAAIGmiCAAASJooAgAAkiaKAACApIkiAAAgaaIIAABImigCAACSJooAAICkiSIAACBpoggAAEiaKAIAAJImigAAgKSJIgAAIGmiCAAASJooAgAAkiaKAACApIkiAAAgaaIIAABImigCAACSJooAAICkiSIAACBpoggAAEiaKAIAAJImigAAgKSJIgAAIGmiCAAASJooAgAAkiaKAACApIkiAAAgaaIIAABImigCAACSJooAAICkiSIAACBpoggAAEiaKAIAAJImigAAgKSJIgAAIGmiCAAASJooAgAAkiaKAACApIkiAAAgaaIIAABImigCAACSJooAAICkiSIAACBpoggAAEiaKAIAAJImigAAgKSJIgAAIGmiCAAASJooAgAAkiaKAACApIkiAAAgaaIIAABImigCAACSJooAAICkiSIAACBpoggAAEiaKAIAAJImigAAgKSJIgAAIGmiCAAASNqQoqipqSmmT58eJSUlUVVVFZs2bfrA+e+8804sWbIkpkyZErlcLs4888zYsGHDkDYMAAAwnCbku2DdunVRX18fq1evjqqqqli1alXU1tbGjh07YvLkyYfN7+npia9+9asxefLkeOKJJ2LatGnx5ptvximnnDIc+wcAADgmBVmWZfksqKqqigsuuCDuv//+iIjo6+uLysrKuPHGG2Pp0qWHzV+9enX8/Oc/j+3bt8cJJ5wwpE12dXVFWVlZdHZ2Rmlp6ZCeAwAAGP9Gog3yevtcT09PbN68OWpqav77BIWFUVNTE62trYOueeaZZ6K6ujqWLFkS5eXlcc4558SKFSuit7f3iNc5ePBgdHV1DXgAAACMhLyiaP/+/dHb2xvl5eUDxsvLy6O9vX3QNTt37ownnngient7Y8OGDXHnnXfGvffeGz/+8Y+PeJ3GxsYoKyvrf1RWVuazTQAAgKM24p8+19fXF5MnT44HH3wwZs2aFXV1dXH77bfH6tWrj7hm2bJl0dnZ2f/Ys2fPSG8TAABIVF4ftDBp0qQoKiqKjo6OAeMdHR1RUVEx6JopU6bECSecEEVFRf1jn/vc56K9vT16enqiuLj4sDW5XC5yuVw+WwMAABiSvO4UFRcXx6xZs6KlpaV/rK+vL1paWqK6unrQNRdddFG8/vrr0dfX1z/22muvxZQpUwYNIgAAgNGU99vn6uvrY82aNfHrX/86tm3bFt/5zneiu7s7Fi9eHBERCxcujGXLlvXP/853vhN///vf46abborXXnst1q9fHytWrIglS5YM36sAAAAYory/p6iuri727dsXy5cvj/b29pg5c2Y0Nzf3f/jC7t27o7Dwv61VWVkZzz33XNxyyy1x3nnnxbRp0+Kmm26KW2+9dfheBQAAwBDl/T1FY8H3FAEAABEfge8pAgAAON6IIgAAIGmiCAAASJooAgAAkiaKAACApIkiAAAgaaIIAABImigCAACSJooAAICkiSIAACBpoggAAEiaKAIAAJImigAAgKSJIgAAIGmiCAAASJooAgAAkiaKAACApIkiAAAgaaIIAABImigCAACSJooAAICkiSIAACBpoggAAEiaKAIAAJImigAAgKSJIgAAIGmiCAAASJooAgAAkiaKAACApIkiAAAgaaIIAABImigCAACSJooAAICkiSIAACBpoggAAEiaKAIAAJImigAAgKSJIgAAIGmiCAAASJooAgAAkiaKAACApIkiAAAgaaIIAABImigCAACSJooAAICkiSIAACBpoggAAEiaKAIAAJImigAAgKSJIgAAIGmiCAAASJooAgAAkiaKAACApIkiAAAgaaIIAABImigCAACSJooAAICkiSIAACBpoggAAEiaKAIAAJImigAAgKSJIgAAIGmiCAAASJooAgAAkiaKAACApIkiAAAgaUOKoqamppg+fXqUlJREVVVVbNq06ajWrV27NgoKCmLevHlDuSwAAMCwyzuK1q1bF/X19dHQ0BBbtmyJGTNmRG1tbezdu/cD1+3atSu+973vxcUXXzzkzQIAAAy3vKPovvvui29961uxePHi+PznPx+rV6+Ok046KR555JEjrunt7Y2rr7467rrrrjj99NOPacMAAADDKa8o6unpic2bN0dNTc1/n6CwMGpqaqK1tfWI6370ox/F5MmT45prrjmq6xw8eDC6uroGPAAAAEZCXlG0f//+6O3tjfLy8gHj5eXl0d7ePuiaP/zhD/Hwww/HmjVrjvo6jY2NUVZW1v+orKzMZ5sAAABHbUQ/fe7AgQOxYMGCWLNmTUyaNOmo1y1btiw6Ozv7H3v27BnBXQIAACmbkM/kSZMmRVFRUXR0dAwY7+jLine truncated
|
||||
"text/plain": [
|
||||
"<Figure size 1000x500 with 1 Axes>"
|
||||
]
|
||||
},
|
||||
"metadata": {},
|
||||
"output_type": "display_data"
|
||||
"name": "stderr",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Python REPL can execute arbitrary code. Use with caution.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"image/png": "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 truncated
|
||||
"image/png": "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 truncated
|
||||
"text/plain": [
|
||||
"<Figure size 1000x500 with 1 Axes>"
|
||||
"<Figure size 1000x600 with 1 Axes>"
|
||||
]
|
||||
},
|
||||
"metadata": {},
|
||||
@@ -490,11 +360,7 @@
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"{'chart_generator': {'messages': [AIMessage(content=\"Unfortunately, I made an error in the provided code. There was a mismatch in the dimensions of the 'Year' and 'GDP (Billion USD)' data arrays, which caused a ValueError. Additionally, I mistakenly included placeholders for the years 2022 and 2023 without having the actual GDP data for those years.\\n\\nLet's correct this and generate a line graph with the data we have up to 2021. I will revise the code to only include the years for which we have data, and then we can create the graph accordingly.\", additional_kwargs={'tool_calls': [{'id': 'call_1zGQMGouC0oFQJRUkNPvs9zX', 'function': {'arguments': '{\"code\":\"import matplotlib.pyplot as plt\\\\n\\\\n# UK GDP data obtained from search results for 2018-2021 (in billion USD)\\\\n# No data for 2022 and 2023 are available\\\\n# Note: 2021 data is used as a placeholder and should be updated when actual figures are available\\\\ngdp_data = {\\\\n \\'Year\\': [2018, 2019, 2020, 2021],\\\\n \\'GDP (Billion USD)\\': [2851.41, 2851.41, 2697.81, 3141.51]\\\\n}\\\\n\\\\n# Plot a line graph\\\\nplt.figure(figsize=(10, 5))\\\\nplt.plot(gdp_data[\\'Year\\'], gdp_data[\\'GDP (Billion USD)\\'], marker=\\'o\\')\\\\n\\\\n# Title and labels\\\\nplt.title(\\'UK GDP from 2018 to 2021\\')\\\\nplt.xlabel(\\'Year\\')\\\\nplt.ylabel(\\'GDP (Billion USD)\\')\\\\n\\\\n# Show grid\\\\nplt.grid(True)\\\\n\\\\n# Display the graph\\\\nplt.show()\"}', 'name': 'python_repl'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 359, 'prompt_tokens': 12796, 'total_tokens': 13155}, 'model_name': 'gpt-4o', 'system_fingerprint': None, 'finish_reason': 'tool_calls', 'logprobs': None}, name='chart_generator', id='run-0d4a67d2-696a-4955-990b-9a9d775b7635-0', tool_calls=[{'name': 'python_repl', 'args': {'code': \"import matplotlib.pyplot as plt\\n\\n# UK GDP data obtained from search results for 2018-2021 (in billion USD)\\n# No data for 2022 and 2023 are available\\n# Note: 2021 data is used as a placeholder and should be updated when actual figures are available\\ngdp_data = {\\n 'Year': [2018, 2019, 2020, 2021],\\n 'GDP (Billion USD)': [2851.41, 2851.41, 2697.81, 3141.51]\\n}\\n\\n# Plot a line graph\\nplt.figure(figsize=(10, 5))\\nplt.plot(gdp_data['Year'], gdp_data['GDP (Billion USD)'], marker='o')\\n\\n# Title and labels\\nplt.title('UK GDP from 2018 to 2021')\\nplt.xlabel('Year')\\nplt.ylabel('GDP (Billion USD)')\\n\\n# Show grid\\nplt.grid(True)\\n\\n# Display the graph\\nplt.show()\"}, 'id': 'call_1zGQMGouC0oFQJRUkNPvs9zX'}])], 'sender': 'chart_generator'}}\n",
|
||||
"----\n",
|
||||
"{'call_tool': {'messages': [ToolMessage(content=\"Successfully executed:\\n```python\\nimport matplotlib.pyplot as plt\\n\\n# UK GDP data obtained from search results for 2018-2021 (in billion USD)\\n# No data for 2022 and 2023 are available\\n# Note: 2021 data is used as a placeholder and should be updated when actual figures are available\\ngdp_data = {\\n 'Year': [2018, 2019, 2020, 2021],\\n 'GDP (Billion USD)': [2851.41, 2851.41, 2697.81, 3141.51]\\n}\\n\\n# Plot a line graph\\nplt.figure(figsize=(10, 5))\\nplt.plot(gdp_data['Year'], gdp_data['GDP (Billion USD)'], marker='o')\\n\\n# Title and labels\\nplt.title('UK GDP from 2018 to 2021')\\nplt.xlabel('Year')\\nplt.ylabel('GDP (Billion USD)')\\n\\n# Show grid\\nplt.grid(True)\\n\\n# Display the graph\\nplt.show()\\n```\\nStdout: \\n\\nIf you have completed all tasks, respond with FINAL ANSWER.\", name='python_repl', tool_call_id='call_1zGQMGouC0oFQJRUkNPvs9zX')]}}\n",
|
||||
"----\n",
|
||||
"{'chart_generator': {'messages': [AIMessage(content=\"FINAL ANSWER\\n\\nI have generated a line graph for the UK's GDP from 2018 to 2021 using the available data. Unfortunately, due to the lack of data for 2022 and 2023, the graph only includes figures up to 2021. Here is the graph:\\n\\n[Graph Image]\\n\\nPlease note that the data for 2022 and 2023 should be added to this graph once it becomes available to complete the analysis for the past five years.\", response_metadata={'token_usage': {'completion_tokens': 99, 'prompt_tokens': 13412, 'total_tokens': 13511}, 'model_name': 'gpt-4o', 'system_fingerprint': None, 'finish_reason': 'stop', 'logprobs': None}, name='chart_generator', id='run-3474a61c-0773-4e44-bd6e-2e88cf56bb90-0')], 'sender': 'chart_generator'}}\n",
|
||||
"{'chart_generator': {'messages': [HumanMessage(content=\"First, get the UK's GDP over the past 5 years, then make a line chart of it. Once you make the chart, finish.\", additional_kwargs={}, response_metadata={}, id='fa1f5e95-9e1a-47d4-b4b6-e93f345e339d'), AIMessage(content=[{'text': \"I'll help search for the UK's GDP data over the past 5 years. Then my colleague can help create the line chart.\", 'type': 'text'}, {'id': 'toolu_01Jd9dxa4Ss2NhzBhCuwUX3E', 'input': {'query': 'UK GDP annual data past 5 years 2019-2023'}, 'name': 'tavily_search_results_json', 'type': 'tool_use'}], additional_kwargs={}, response_metadata={'id': 'msg_014nCkfVHnG6LAsiS6pY7zcd', 'model': 'claude-3-5-sonnet-20241022', 'stop_reason': 'tool_use', 'stop_sequence': None, 'usage': {'input_tokens': 555, 'output_tokens': 101}}, id='run-e2297529-9972-4de6-835d-23d920b0e29b-0', tool_calls=[{'name': 'tavily_search_results_json', 'args': {'query': 'UK GDP annual data past 5 years 2019-2023'}, 'id': 'toolu_01Jd9dxa4Ss2NhzBhCuwUX3E', 'type': 'tool_call'}], usage_metadata={'input_tokens': 555, 'output_tokens': 101, 'total_tokens': 656, 'input_token_details': {}}), ToolMessage(content='[{\"url\": \"https://www.macrotrends.net/global-metrics/countries/GBR/united-kingdom/gdp-gross-domestic-product\", \"content\": \"Dollar figures for GDP are converted from domestic currencies using single year official exchange rates. For a few countries where the official exchange rate does not reflect the rate effectively applied to actual foreign exchange transactions, an alternative conversion factor is used. U.K. gdp for 2023 was $3,340.03B, a 8.13% increase from 2022.\"}, {\"url\": \"https://www.statista.com/topics/3795/gdp-of-the-uk/\", \"content\": \"Monthly growth of gross domestic product in the United Kingdom from January 2019 to November 2023\\\\nContribution to GDP growth in the UK 2023, by sector\\\\nContribution to gross domestic product growth in the United Kingdom in January 2023, by sector\\\\nGDP growth rate in the UK 1999-2021, by country\\\\nAnnual growth rates of gross domestic product in the United Kingdom from 1999 to 2021, by country\\\\nGDP growth rate in the UK 2021, by region\\\\nAnnual growth rates of gross domestic product in the United Kingdom in 2021, by region\\\\nGDP growth of Scotland 2021, by local area\\\\nAnnual growth rates of gross domestic product in Scotland in 2021, by local (ITL 3) area\\\\nGDP growth of Wales 2021, by local area\\\\nAnnual growth rates of gross domestic product in Wales in 2021, by local (ITL 3) area\\\\nGDP growth of Northern Ireland 2021, by local area\\\\nAnnual growth rates of gross domestic product in Northern Ireland in 2021, by local (ITL 3) area\\\\nGDP per capita\\\\nGDP per capita\\\\nGDP per capita in the UK 1955-2022\\\\nGross domestic product per capita in the United Kingdom from 1955 to 2022 (in GBP)\\\\nAnnual GDP per capita growth in the UK 1956-2022\\\\nAnnual GDP per capita growth in the United Kingdom from 1956 to 2022\\\\nQuarterly GDP per capita in the UK 2019-2023\\\\nQuarterly GDP per capita in the United Kingdom from 1st quarter 2019 to 3rd quarter 2023 (in GBP)\\\\nQuarterly GDP per capita growth in the UK 2019-2023\\\\nQuarterly GDP per capita growth in the United Kingdom from 1st quarter 2019 to 3rd quarter 2023 (in GBP)\\\\nGDP per capita of the UK 1999-2021, by country\\\\nGross domestic product per capita of the United Kingdom from 1999 to 2021, by country (in GBP)\\\\nGDP per capita of the UK 2021, by region\\\\nGross domestic product per capita of the United Kingdom in 2021, by region (in GBP)\\\\nGlobal Comparisons\\\\nGlobal Comparisons\\\\nCountries with the largest gross domestic product (GDP) 2022\\\\n Monthly GDP of the UK 2019-2023\\\\nMonthly index of gross domestic product in the United Kingdom from January 2019 to November 2023 (2019=100)\\\\nGVA of the UK 2022, by sector\\\\nGross value added of the United Kingdom in 2022, by industry sector (in million GBP)\\\\nGDP of the UK 2021, by country\\\\nGross domestic product of the United Kingdom in 2021, by country (in million GBP)\\\\nGDP of the UK 2021, by region\\\\nGross domestic product of the United Kingdom in 2021, by region (in million GBP)\\\\nGDP of Scotland 2021, by local area\\\\nGross domestic product of Scotland in 2021, by local (ITL 3) area (in million GBP)\\\\nGDP of Wales 2021, by local area\\\\nGross domestic product of Wales in 2021, by local (ITL 3) area (in million GBP)\\\\nGDP of Northern Ireland 2021, by local area\\\\nGross domestic product of Northern Ireland in 2021, by local (ITL 3) area (in million GBP)\\\\nGDP growth\\\\nGDP growth\\\\nGDP growth forecast for the UK 2000-2028\\\\nForecasted annual growth of gross domestic product in the United Kingdom from 2000 to 2028\\\\nAnnual GDP growth in the UK 1949-2022\\\\nAnnual growth of gross domestic product in the United Kingdom from 1949 to 2022\\\\nQuarterly GDP growth of the UK 2019-2023\\\\nQuarterly growth of gross domestic proLine truncated
|
||||
"----\n"
|
||||
]
|
||||
}
|
||||
@@ -503,10 +369,10 @@
|
||||
"events = graph.stream(\n",
|
||||
" {\n",
|
||||
" \"messages\": [\n",
|
||||
" HumanMessage(\n",
|
||||
" content=\"Fetch the UK's GDP over the past 5 years,\"\n",
|
||||
" \" then draw a line graph of it.\"\n",
|
||||
" \" Once you code it up, finish.\"\n",
|
||||
" (\n",
|
||||
" \"user\",\n",
|
||||
" \"First, get the UK's GDP over the past 5 years, then make a line chart of it. \"\n",
|
||||
" \"Once you make the chart, finish.\",\n",
|
||||
" )\n",
|
||||
" ],\n",
|
||||
" },\n",
|
||||
|
||||
@@ -112,7 +112,7 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"_set_env(\"LANGCHAIN_API_KEY\")\n",
|
||||
"_set_env(\"LANGSMITH_API_KEY\")\n",
|
||||
"os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n",
|
||||
"os.environ[\"LANGCHAIN_PROJECT\"] = \"local-llama32-rag\""
|
||||
]
|
||||
|
||||
@@ -3,7 +3,7 @@ from contextlib import contextmanager
|
||||
from typing import Any, Iterator, Optional, Sequence, Union
|
||||
|
||||
from langchain_core.runnables import RunnableConfig
|
||||
from psycopg import Connection, Cursor, Pipeline
|
||||
from psycopg import Capabilities, Connection, Cursor, Pipeline
|
||||
from psycopg.errors import UndefinedTable
|
||||
from psycopg.rows import DictRow, dict_row
|
||||
from psycopg.types.json import Jsonb
|
||||
@@ -52,6 +52,7 @@ class PostgresSaver(BasePostgresSaver):
|
||||
self.conn = conn
|
||||
self.pipe = pipe
|
||||
self.lock = threading.Lock()
|
||||
self.supports_pipeline = Capabilities().has_pipeline()
|
||||
|
||||
@classmethod
|
||||
@contextmanager
|
||||
@@ -365,6 +366,13 @@ class PostgresSaver(BasePostgresSaver):
|
||||
|
||||
@contextmanager
|
||||
def _cursor(self, *, pipeline: bool = False) -> Iterator[Cursor[DictRow]]:
|
||||
"""Create a database cursor as a context manager.
|
||||
|
||||
Args:
|
||||
pipeline (bool): whether to use pipeline for the DB operations inside the context manager.
|
||||
Will be applied regardless of whether the PostgresSaver instance was initialized with a pipeline.
|
||||
If pipeline mode is not supported, will fall back to using transaction context manager.
|
||||
"""
|
||||
with _get_connection(self.conn) as conn:
|
||||
if self.pipe:
|
||||
# a connection in pipeline mode can be used concurrently
|
||||
@@ -379,10 +387,17 @@ class PostgresSaver(BasePostgresSaver):
|
||||
elif pipeline:
|
||||
# a connection not in pipeline mode can only be used by one
|
||||
# thread/coroutine at a time, so we acquire a lock
|
||||
with self.lock, conn.pipeline(), conn.cursor(
|
||||
binary=True, row_factory=dict_row
|
||||
) as cur:
|
||||
yield cur
|
||||
if self.supports_pipeline:
|
||||
with self.lock, conn.pipeline(), conn.cursor(
|
||||
binary=True, row_factory=dict_row
|
||||
) as cur:
|
||||
yield cur
|
||||
else:
|
||||
# Use connection's transaction context manager when pipeline mode not supported
|
||||
with self.lock, conn.transaction(), conn.cursor(
|
||||
binary=True, row_factory=dict_row
|
||||
) as cur:
|
||||
yield cur
|
||||
else:
|
||||
with self.lock, conn.cursor(binary=True, row_factory=dict_row) as cur:
|
||||
yield cur
|
||||
|
||||
@@ -3,7 +3,7 @@ from contextlib import asynccontextmanager
|
||||
from typing import Any, AsyncIterator, Iterator, Optional, Sequence, Union
|
||||
|
||||
from langchain_core.runnables import RunnableConfig
|
||||
from psycopg import AsyncConnection, AsyncCursor, AsyncPipeline
|
||||
from psycopg import AsyncConnection, AsyncCursor, AsyncPipeline, Capabilities
|
||||
from psycopg.errors import UndefinedTable
|
||||
from psycopg.rows import DictRow, dict_row
|
||||
from psycopg.types.json import Jsonb
|
||||
@@ -55,6 +55,7 @@ class AsyncPostgresSaver(BasePostgresSaver):
|
||||
self.pipe = pipe
|
||||
self.lock = asyncio.Lock()
|
||||
self.loop = asyncio.get_running_loop()
|
||||
self.supports_pipeline = Capabilities().has_pipeline()
|
||||
|
||||
@classmethod
|
||||
@asynccontextmanager
|
||||
@@ -323,6 +324,13 @@ class AsyncPostgresSaver(BasePostgresSaver):
|
||||
async def _cursor(
|
||||
self, *, pipeline: bool = False
|
||||
) -> AsyncIterator[AsyncCursor[DictRow]]:
|
||||
"""Create a database cursor as a context manager.
|
||||
|
||||
Args:
|
||||
pipeline (bool): whether to use pipeline for the DB operations inside the context manager.
|
||||
Will be applied regardless of whether the AsyncPostgresSaver instance was initialized with a pipeline.
|
||||
If pipeline mode is not supported, will fall back to using transaction context manager.
|
||||
"""
|
||||
async with _get_connection(self.conn) as conn:
|
||||
if self.pipe:
|
||||
# a connection in pipeline mode can be used concurrently
|
||||
@@ -337,10 +345,17 @@ class AsyncPostgresSaver(BasePostgresSaver):
|
||||
elif pipeline:
|
||||
# a connection not in pipeline mode can only be used by one
|
||||
# thread/coroutine at a time, so we acquire a lock
|
||||
async with self.lock, conn.pipeline(), conn.cursor(
|
||||
binary=True, row_factory=dict_row
|
||||
) as cur:
|
||||
yield cur
|
||||
if self.supports_pipeline:
|
||||
async with self.lock, conn.pipeline(), conn.cursor(
|
||||
binary=True, row_factory=dict_row
|
||||
) as cur:
|
||||
yield cur
|
||||
else:
|
||||
# Use connection's transaction context manager when pipeline mode not supported
|
||||
async with self.lock, conn.transaction(), conn.cursor(
|
||||
binary=True, row_factory=dict_row
|
||||
) as cur:
|
||||
yield cur
|
||||
else:
|
||||
async with self.lock, conn.cursor(
|
||||
binary=True, row_factory=dict_row
|
||||
|
||||
@@ -133,6 +133,7 @@ class BasePostgresSaver(BaseCheckpointSaver[str]):
|
||||
INSERT_CHECKPOINT_WRITES_SQL = INSERT_CHECKPOINT_WRITES_SQL
|
||||
|
||||
jsonplus_serde = JsonPlusSerializer()
|
||||
supports_pipeline: bool
|
||||
|
||||
def _load_checkpoint(
|
||||
self,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "langgraph-checkpoint-postgres"
|
||||
version = "2.0.2"
|
||||
version = "2.0.3"
|
||||
description = "Library with a Postgres implementation of LangGraph checkpoint saver."
|
||||
authors = []
|
||||
license = "MIT"
|
||||
|
||||
@@ -24,6 +24,8 @@ from langgraph.checkpoint.serde.base import SerializerProtocol, maybe_add_typed_
|
||||
from langgraph.checkpoint.serde.jsonplus import JsonPlusSerializer
|
||||
from langgraph.checkpoint.serde.types import (
|
||||
ERROR,
|
||||
INTERRUPT,
|
||||
RESUME,
|
||||
SCHEDULED,
|
||||
ChannelProtocol,
|
||||
SendProtocol,
|
||||
@@ -37,12 +39,13 @@ PendingWrite = Tuple[str, str, Any]
|
||||
class CheckpointMetadata(TypedDict, total=False):
|
||||
"""Metadata associated with a checkpoint."""
|
||||
|
||||
source: Literal["input", "loop", "update"]
|
||||
source: Literal["input", "loop", "update", "fork"]
|
||||
"""The source of the checkpoint.
|
||||
|
||||
- "input": The checkpoint was created from an input to invoke/stream/batch.
|
||||
- "loop": The checkpoint was created from inside the pregel loop.
|
||||
- "update": The checkpoint was created from a manual state update.
|
||||
- "fork": The checkpoint was created as a copy of another checkpoint.
|
||||
"""
|
||||
step: int
|
||||
"""The step number of the checkpoint.
|
||||
@@ -449,4 +452,4 @@ Special writes (e.g. errors) map to negative indices, to avoid those writes from
|
||||
conflicting with regular writes.
|
||||
Each Checkpointer implementation should use this mapping in put_writes.
|
||||
"""
|
||||
WRITES_IDX_MAP = {ERROR: -1, SCHEDULED: -2}
|
||||
WRITES_IDX_MAP = {ERROR: -1, SCHEDULED: -2, INTERRUPT: -3, RESUME: -4}
|
||||
@@ -1,10 +1,14 @@
|
||||
import asyncio
|
||||
import logging
|
||||
import os
|
||||
import pickle
|
||||
import random
|
||||
import shutil
|
||||
from collections import defaultdict
|
||||
from contextlib import AbstractAsyncContextManager, AbstractContextManager
|
||||
from contextlib import AbstractAsyncContextManager, AbstractContextManager, ExitStack
|
||||
from functools import partial
|
||||
from types import TracebackType
|
||||
from typing import Any, AsyncIterator, Dict, Iterator, Optional, Sequence, Tuple
|
||||
from typing import Any, AsyncIterator, Dict, Iterator, Optional, Sequence, Tuple, Type
|
||||
|
||||
from langchain_core.runnables import RunnableConfig
|
||||
|
||||
@@ -20,6 +24,8 @@ from langgraph.checkpoint.base import (
|
||||
)
|
||||
from langgraph.checkpoint.serde.types import TASKS, ChannelProtocol
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class MemorySaver(
|
||||
BaseCheckpointSaver[str], AbstractContextManager, AbstractAsyncContextManager
|
||||
@@ -68,13 +74,18 @@ class MemorySaver(
|
||||
self,
|
||||
*,
|
||||
serde: Optional[SerializerProtocol] = None,
|
||||
factory: Type[defaultdict] = defaultdict,
|
||||
) -> None:
|
||||
super().__init__(serde=serde)
|
||||
self.storage = defaultdict(lambda: defaultdict(dict))
|
||||
self.writes = defaultdict(dict)
|
||||
self.storage = factory(lambda: defaultdict(dict))
|
||||
self.writes = factory(dict)
|
||||
self.stack = ExitStack()
|
||||
if factory is not defaultdict:
|
||||
self.stack.enter_context(self.storage) # type: ignore[arg-type]
|
||||
self.stack.enter_context(self.writes) # type: ignore[arg-type]
|
||||
|
||||
def __enter__(self) -> "MemorySaver":
|
||||
return self
|
||||
return self.stack.__enter__()
|
||||
|
||||
def __exit__(
|
||||
self,
|
||||
@@ -82,10 +93,10 @@ class MemorySaver(
|
||||
exc_value: Optional[BaseException],
|
||||
traceback: Optional[TracebackType],
|
||||
) -> Optional[bool]:
|
||||
return
|
||||
return self.stack.__exit__(exc_type, exc_value, traceback)
|
||||
|
||||
async def __aenter__(self) -> "MemorySaver":
|
||||
return self
|
||||
return self.stack.__enter__()
|
||||
|
||||
async def __aexit__(
|
||||
self,
|
||||
@@ -93,7 +104,7 @@ class MemorySaver(
|
||||
__exc_value: Optional[BaseException],
|
||||
__traceback: Optional[TracebackType],
|
||||
) -> Optional[bool]:
|
||||
return
|
||||
return self.stack.__exit__(__exc_type, __exc_value, __traceback)
|
||||
|
||||
def get_tuple(self, config: RunnableConfig) -> Optional[CheckpointTuple]:
|
||||
"""Get a checkpoint tuple from the in-memory storage.
|
||||
@@ -361,7 +372,7 @@ class MemorySaver(
|
||||
RunnableConfig: The updated config containing the saved writes' timestamp.
|
||||
"""
|
||||
thread_id = config["configurable"]["thread_id"]
|
||||
checkpoint_ns = config["configurable"]["checkpoint_ns"]
|
||||
checkpoint_ns = config["configurable"].get("checkpoint_ns", "")
|
||||
checkpoint_id = config["configurable"]["checkpoint_id"]
|
||||
outer_key = (thread_id, checkpoint_ns, checkpoint_id)
|
||||
outer_writes_ = self.writes.get(outer_key)
|
||||
@@ -478,3 +489,76 @@ class MemorySaver(
|
||||
next_v = current_v + 1
|
||||
next_h = random.random()
|
||||
return f"{next_v:032}.{next_h:016}"
|
||||
|
||||
|
||||
class PersistentDict(defaultdict):
|
||||
"""Persistent dictionary with an API compatible with shelve and anydbm.
|
||||
|
||||
The dict is kept in memory, so the dictionary operations run as fast as
|
||||
a regular dictionary.
|
||||
|
||||
Write to disk is delayed until close or sync (similar to gdbm's fast mode).
|
||||
|
||||
Input file format is automatically discovered.
|
||||
Output file format is selectable between pickle, json, and csv.
|
||||
All three serialization formats are backed by fast C implementations.
|
||||
|
||||
Adapted from https://code.activestate.com/recipes/576642-persistent-dict-with-multiple-standard-file-format/
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, *args: Any, filename: str, **kwds: Any) -> None:
|
||||
self.flag = "c" # r=readonly, c=create, or n=new
|
||||
self.mode = None # None or an octal triple like 0644
|
||||
self.format = "pickle" # 'csv', 'json', or 'pickle'
|
||||
self.filename = filename
|
||||
super().__init__(*args, **kwds)
|
||||
|
||||
def sync(self) -> None:
|
||||
"Write dict to disk"
|
||||
if self.flag == "r":
|
||||
return
|
||||
tempname = self.filename + ".tmp"
|
||||
fileobj = open(tempname, "wb" if self.format == "pickle" else "w")
|
||||
try:
|
||||
self.dump(fileobj)
|
||||
except Exception:
|
||||
os.remove(tempname)
|
||||
raise
|
||||
finally:
|
||||
fileobj.close()
|
||||
shutil.move(tempname, self.filename) # atomic commit
|
||||
if self.mode is not None:
|
||||
os.chmod(self.filename, self.mode)
|
||||
|
||||
def close(self) -> None:
|
||||
self.sync()
|
||||
self.clear()
|
||||
|
||||
def __enter__(self) -> "PersistentDict":
|
||||
return self
|
||||
|
||||
def __exit__(self, *exc_info: Any) -> None:
|
||||
self.close()
|
||||
|
||||
def dump(self, fileobj: Any) -> None:
|
||||
if self.format == "pickle":
|
||||
pickle.dump(dict(self), fileobj, 2)
|
||||
else:
|
||||
raise NotImplementedError("Unknown format: " + repr(self.format))
|
||||
|
||||
def load(self) -> None:
|
||||
# try formats from most restrictive to least restrictive
|
||||
if self.flag == "n":
|
||||
return
|
||||
with open(self.filename, "rb" if self.format == "pickle" else "r") as fileobj:
|
||||
for loader in (pickle.load,):
|
||||
fileobj.seek(0)
|
||||
try:
|
||||
return self.update(loader(fileobj))
|
||||
except EOFError:
|
||||
return
|
||||
except Exception:
|
||||
logging.error(f"Failed to load file: {fileobj.name}")
|
||||
raise
|
||||
raise ValueError("File not in a supported f ormat")
|
||||
@@ -25,7 +25,7 @@ from langchain_core.load.serializable import Serializable
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
from langgraph.checkpoint.serde.base import SerializerProtocol
|
||||
from langgraph.checkpoint.serde.types import CommandProtocol, SendProtocol
|
||||
from langgraph.checkpoint.serde.types import SendProtocol
|
||||
from langgraph.store.base import Item
|
||||
|
||||
LC_REVIVER = Reviver()
|
||||
@@ -122,11 +122,6 @@ class JsonPlusSerializer(SerializerProtocol):
|
||||
return self._encode_constructor_args(
|
||||
obj.__class__, kwargs={"node": obj.node, "arg": obj.arg}
|
||||
)
|
||||
elif isinstance(obj, CommandProtocol):
|
||||
return self._encode_constructor_args(
|
||||
obj.__class__,
|
||||
kwargs={k: getattr(obj, k) for k in obj.__all_slots__},
|
||||
)
|
||||
elif isinstance(obj, (bytes, bytearray)):
|
||||
return self._encode_constructor_args(
|
||||
obj.__class__, method="fromhex", args=(obj.hex(),)
|
||||
@@ -407,17 +402,6 @@ def _msgpack_default(obj: Any) -> Union[str, msgpack.ExtType]:
|
||||
(obj.__class__.__module__, obj.__class__.__name__, (obj.node, obj.arg)),
|
||||
),
|
||||
)
|
||||
elif isinstance(obj, CommandProtocol):
|
||||
return msgpack.ExtType(
|
||||
EXT_CONSTRUCTOR_KW_ARGS,
|
||||
_msgpack_enc(
|
||||
(
|
||||
obj.__class__.__module__,
|
||||
obj.__class__.__name__,
|
||||
{k: getattr(obj, k) for k in obj.__all_slots__},
|
||||
),
|
||||
),
|
||||
)
|
||||
elif dataclasses.is_dataclass(obj):
|
||||
# doesn't use dataclasses.asdict to avoid deepcopy and recursion
|
||||
return msgpack.ExtType(
|
||||
|
||||
@@ -4,7 +4,6 @@ from typing import (
|
||||
Protocol,
|
||||
Sequence,
|
||||
TypeVar,
|
||||
Union,
|
||||
runtime_checkable,
|
||||
)
|
||||
|
||||
@@ -12,6 +11,8 @@ from typing_extensions import Self
|
||||
|
||||
ERROR = "__error__"
|
||||
SCHEDULED = "__scheduled__"
|
||||
INTERRUPT = "__interrupt__"
|
||||
RESUME = "__resume__"
|
||||
TASKS = "__pregel_tasks"
|
||||
|
||||
Value = TypeVar("Value", covariant=True)
|
||||
@@ -49,11 +50,3 @@ class SendProtocol(Protocol):
|
||||
def __repr__(self) -> str: ...
|
||||
|
||||
def __eq__(self, value: object) -> bool: ...
|
||||
|
||||
|
||||
@runtime_checkable
|
||||
class CommandProtocol(Protocol):
|
||||
# Mirrors langgraph.types.Command
|
||||
update: Optional[dict[str, Any]]
|
||||
send: Union[Any, Sequence[Any]]
|
||||
__all_slots__: set[str]
|
||||
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "langgraph-checkpoint"
|
||||
version = "2.0.3"
|
||||
version = "2.0.5"
|
||||
description = "Library with base interfaces for LangGraph checkpoint savers."
|
||||
authors = []
|
||||
license = "MIT"
|
||||
|
||||
@@ -314,7 +314,7 @@ async def test_cannot_put_empty_namespace() -> None:
|
||||
assert store.get(("langgraph", "foo"), "bar") is None
|
||||
|
||||
class MockAsyncBatchedStore(AsyncBatchedBaseStore):
|
||||
def __init__(self):
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self._store = InMemoryStore()
|
||||
|
||||
@@ -340,13 +340,17 @@ async def test_cannot_put_empty_namespace() -> None:
|
||||
await async_store.aput(("langgraph", "foo"), "bar", doc)
|
||||
|
||||
await async_store.aput(("foo", "langgraph", "foo"), "bar", doc)
|
||||
assert (await async_store.aget(("foo", "langgraph", "foo"), "bar")).value == doc
|
||||
val = await async_store.aget(("foo", "langgraph", "foo"), "bar")
|
||||
assert val is not None
|
||||
assert val.value == doc
|
||||
assert (await async_store.asearch(("foo", "langgraph", "foo")))[0].value == doc
|
||||
await async_store.adelete(("foo", "langgraph", "foo"), "bar")
|
||||
assert (await async_store.aget(("foo", "langgraph", "foo"), "bar")) is None
|
||||
|
||||
await async_store.abatch([PutOp(("valid", "namespace"), "key", doc)])
|
||||
assert (await async_store.aget(("valid", "namespace"), "key")).value == doc
|
||||
val = await async_store.aget(("valid", "namespace"), "key")
|
||||
assert val is not None
|
||||
assert val.value == doc
|
||||
assert (await async_store.asearch(("valid", "namespace")))[0].value == doc
|
||||
await async_store.adelete(("valid", "namespace"), "key")
|
||||
assert (await async_store.aget(("valid", "namespace"), "key")) is None
|
||||
+103
-8
@@ -1,10 +1,105 @@
|
||||
# langchain-cli
|
||||
# LangGraph CLI
|
||||
|
||||
This package implements the official CLI for LangGraph API.
|
||||
The official command-line interface for LangGraph, providing tools to create, develop, and deploy LangGraph applications.
|
||||
|
||||
## How to Test CLI Changes Locally
|
||||
These instructions are for CLI development and testing. Use the CLI examples to test CLI changes locally.
|
||||
1. Make changes to the CLI code.
|
||||
1. Navigate to the `libs/cli/examples`: `cd libs/cli/examples`
|
||||
1. Install CLI examples dependencies: `poetry install`
|
||||
1. Run/test CLI command (e.g. `langgraph build`).
|
||||
## Installation
|
||||
|
||||
Install via pip:
|
||||
```bash
|
||||
pip install langgraph-cli
|
||||
```
|
||||
|
||||
For development mode with hot reloading:
|
||||
```bash
|
||||
pip install "langgraph-cli[inmem]"
|
||||
```
|
||||
|
||||
## Commands
|
||||
|
||||
### `langgraph new` 🌱
|
||||
Create a new LangGraph project from a template
|
||||
```bash
|
||||
langgraph new [PATH] --template TEMPLATE_NAME
|
||||
```
|
||||
|
||||
### `langgraph dev` 🏃♀️
|
||||
Run LangGraph API server in development mode with hot reloading
|
||||
```bash
|
||||
langgraph dev [OPTIONS]
|
||||
--host TEXT Host to bind to (default: 127.0.0.1)
|
||||
--port INTEGER Port to bind to (default: 2024)
|
||||
--no-reload Disable auto-reload
|
||||
--debug-port INTEGER Enable remote debugging
|
||||
--no-browser Skip opening browser window
|
||||
-c, --config FILE Config file path (default: langgraph.json)
|
||||
```
|
||||
|
||||
### `langgraph up` 🚀
|
||||
Launch LangGraph API server in Docker
|
||||
```bash
|
||||
langgraph up [OPTIONS]
|
||||
-p, --port INTEGER Port to expose (default: 8123)
|
||||
--wait Wait for services to start
|
||||
--watch Restart on file changes
|
||||
--verbose Show detailed logs
|
||||
-c, --config FILE Config file path
|
||||
-d, --docker-compose Additional services file
|
||||
```
|
||||
|
||||
### `langgraph build`
|
||||
Build a Docker image for your LangGraph application
|
||||
```bash
|
||||
langgraph build -t IMAGE_TAG [OPTIONS]
|
||||
--platform TEXT Target platforms (e.g., linux/amd64,linux/arm64)
|
||||
--pull / --no-pull Use latest/local base image
|
||||
-c, --config FILE Config file path
|
||||
```
|
||||
|
||||
### `langgraph dockerfile`
|
||||
Generate a Dockerfile for custom deployments
|
||||
```bash
|
||||
langgraph dockerfile SAVE_PATH [OPTIONS]
|
||||
-c, --config FILE Config file path
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
The CLI uses a `langgraph.json` configuration file with these key settings:
|
||||
|
||||
```json
|
||||
{
|
||||
"dependencies": ["langchain_openai", "./your_package"], // Required: Package dependencies
|
||||
"graphs": {
|
||||
"my_graph": "./your_package/file.py:graph" // Required: Graph definitions
|
||||
},
|
||||
"env": "./.env", // Optional: Environment variables
|
||||
"python_version": "3.11", // Optional: Python version (3.11/3.12)
|
||||
"pip_config_file": "./pip.conf", // Optional: pip configuration
|
||||
"dockerfile_lines": [] // Optional: Additional Dockerfile commands
|
||||
}
|
||||
```
|
||||
|
||||
See the [full documentation](https://langchain-ai.github.io/langgraph/docs/cloud/reference/cli.html) for detailed configuration options.
|
||||
|
||||
## Development
|
||||
|
||||
To develop the CLI itself:
|
||||
|
||||
1. Clone the repository
|
||||
2. Navigate to the CLI directory: `cd libs/cli`
|
||||
3. Install development dependencies: `poetry install`
|
||||
4. Make your changes to the CLI code
|
||||
5. Test your changes:
|
||||
```bash
|
||||
# Run CLI commands directly
|
||||
poetry run langgraph --help
|
||||
|
||||
# Or use the examples
|
||||
cd examples
|
||||
poetry install
|
||||
poetry run langgraph dev # or other commands
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the terms specified in the repository's LICENSE file.
|
||||
+251
-21
@@ -6,6 +6,7 @@ from typing import Callable, Optional, Sequence
|
||||
|
||||
import click
|
||||
import click.exceptions
|
||||
from click import secho
|
||||
|
||||
import langgraph_cli.config
|
||||
import langgraph_cli.docker
|
||||
@@ -170,9 +171,7 @@ def cli():
|
||||
is_flag=True,
|
||||
help="Wait for services to start before returning. Implies --detach",
|
||||
)
|
||||
@cli.command(
|
||||
help="Start langgraph API server. For local testing, requires a LangSmith API key with access to LangGraph Cloud closed beta. Requires a license key for production use."
|
||||
)
|
||||
@cli.command(help="🚀 Launch LangGraph API server.")
|
||||
@log_command
|
||||
def up(
|
||||
config: pathlib.Path,
|
||||
@@ -286,9 +285,11 @@ def _build(
|
||||
subp_exec(
|
||||
"docker",
|
||||
"pull",
|
||||
f"{base_image}:{config_json['node_version']}"
|
||||
if config_json.get("node_version")
|
||||
else f"{base_image}:{config_json['python_version']}",
|
||||
(
|
||||
f"{base_image}:{config_json['node_version']}"
|
||||
if config_json.get("node_version")
|
||||
else f"{base_image}:{config_json['python_version']}"
|
||||
),
|
||||
verbose=True,
|
||||
)
|
||||
)
|
||||
@@ -337,7 +338,7 @@ def _build(
|
||||
)
|
||||
@click.argument("docker_build_args", nargs=-1, type=click.UNPROCESSED)
|
||||
@cli.command(
|
||||
help="Build langgraph API server docker image",
|
||||
help="📦 Build LangGraph API server Docker image.",
|
||||
context_settings=dict(
|
||||
ignore_unknown_options=True,
|
||||
),
|
||||
@@ -360,23 +361,157 @@ def build(
|
||||
)
|
||||
|
||||
|
||||
def _get_docker_ignore_content() -> str:
|
||||
"""Return the content of a .dockerignore file.
|
||||
|
||||
This file is used to exclude files and directories from the Docker build context.
|
||||
|
||||
It may be overly broad, but it's better to be safe than sorry.
|
||||
|
||||
The main goal is to exclude .env files by default.
|
||||
"""
|
||||
return """\
|
||||
# Ignore node_modules and other dependency directories
|
||||
node_modules
|
||||
bower_components
|
||||
vendor
|
||||
|
||||
# Ignore logs and temporary files
|
||||
*.log
|
||||
*.tmp
|
||||
*.swp
|
||||
|
||||
# Ignore .env files and other environment files
|
||||
.env
|
||||
.env.*
|
||||
*.local
|
||||
|
||||
# Ignore git-related files
|
||||
.git
|
||||
.gitignore
|
||||
|
||||
# Ignore Docker-related files and configs
|
||||
.dockerignore
|
||||
docker-compose.yml
|
||||
|
||||
# Ignore build and cache directories
|
||||
dist
|
||||
build
|
||||
.cache
|
||||
__pycache__
|
||||
|
||||
# Ignore IDE and editor configurations
|
||||
.vscode
|
||||
.idea
|
||||
*.sublime-project
|
||||
*.sublime-workspace
|
||||
.DS_Store # macOS-specific
|
||||
|
||||
# Ignore test and coverage files
|
||||
coverage
|
||||
*.coverage
|
||||
*.test.js
|
||||
*.spec.js
|
||||
tests
|
||||
"""
|
||||
|
||||
|
||||
@OPT_CONFIG
|
||||
@click.argument("save_path", type=click.Path(resolve_path=True))
|
||||
@cli.command(help="Generate a Dockerfile for langgraph API server")
|
||||
@cli.command(
|
||||
help="🐳 Generate a Dockerfile for the LangGraph API server, with Docker Compose options."
|
||||
)
|
||||
@click.option(
|
||||
# Add a flag for adding a docker-compose.yml file as part of the output
|
||||
"--add-docker-compose",
|
||||
help=(
|
||||
"Add additional files for running the LangGraph API server with "
|
||||
"docker-compose. These files include a docker-compose.yml, .env file, "
|
||||
"and a .dockerignore file."
|
||||
),
|
||||
is_flag=True,
|
||||
)
|
||||
@log_command
|
||||
def dockerfile(save_path: pathlib.Path, config: pathlib.Path):
|
||||
with open(config) as f:
|
||||
def dockerfile(save_path: str, config: pathlib.Path, add_docker_compose: bool) -> None:
|
||||
save_path = pathlib.Path(save_path).absolute()
|
||||
secho(f"🔍 Validating configuration at path: {config}", fg="yellow")
|
||||
with open(config, encoding="utf-8") as f:
|
||||
config_json = langgraph_cli.config.validate_config(json.load(f))
|
||||
with open(save_path, "w") as f:
|
||||
secho("✅ Configuration validated!", fg="green")
|
||||
|
||||
secho(f"📝 Generating Dockerfile at {save_path}", fg="yellow")
|
||||
with open(str(save_path), "w", encoding="utf-8") as f:
|
||||
f.write(
|
||||
langgraph_cli.config.config_to_docker(
|
||||
config,
|
||||
config_json,
|
||||
"langchain/langgraphjs-api"
|
||||
if config_json.get("node_version")
|
||||
else "langchain/langgraph-api",
|
||||
(
|
||||
"langchain/langgraphjs-api"
|
||||
if config_json.get("node_version")
|
||||
else "langchain/langgraph-api"
|
||||
),
|
||||
)
|
||||
)
|
||||
secho("✅ Created: Dockerfile", fg="green")
|
||||
|
||||
if add_docker_compose:
|
||||
# Add docker compose and related files
|
||||
# Add .dockerignore file in the same directory as the Dockerfile
|
||||
with open(str(save_path.parent / ".dockerignore"), "w", encoding="utf-8") as f:
|
||||
f.write(_get_docker_ignore_content())
|
||||
secho("✅ Created: .dockerignore", fg="green")
|
||||
|
||||
# Generate a docker-compose.yml file
|
||||
path = str(save_path.parent / "docker-compose.yml")
|
||||
with open(path, "w", encoding="utf-8") as f:
|
||||
with Runner() as runner:
|
||||
capabilities = langgraph_cli.docker.check_capabilities(runner)
|
||||
|
||||
compose_dict = langgraph_cli.docker.compose_as_dict(
|
||||
capabilities,
|
||||
port=8123,
|
||||
)
|
||||
# Add .env file to the docker-compose.yml for the langgraph-api service
|
||||
compose_dict["services"]["langgraph-api"]["env_file"] = [".env"]
|
||||
# Add the Dockerfile to the build context
|
||||
compose_dict["services"]["langgraph-api"]["build"] = {
|
||||
"context": ".",
|
||||
"dockerfile": save_path.name,
|
||||
}
|
||||
f.write(langgraph_cli.docker.dict_to_yaml(compose_dict))
|
||||
secho("✅ Created: docker-compose.yml", fg="green")
|
||||
|
||||
# Check if the .env file exists in the same directory as the Dockerfile
|
||||
if not (save_path.parent / ".env").exists():
|
||||
# Also add an empty .env file
|
||||
with open(str(save_path.parent / ".env"), "w", encoding="utf-8") as f:
|
||||
f.writelines(
|
||||
[
|
||||
"# Uncomment the following line to add your LangSmith API key",
|
||||
"\n",
|
||||
"# LANGSMITH_API_KEY=your-api-key",
|
||||
"\n",
|
||||
"# Or if you have a LangGraph Cloud license key, "
|
||||
"then uncomment the following line: ",
|
||||
"\n",
|
||||
"# LANGGRAPH_CLOUD_LICENSE_KEY=your-license-key",
|
||||
"\n",
|
||||
"# Add any other environment variables go below...",
|
||||
]
|
||||
)
|
||||
|
||||
secho("✅ Created: .env", fg="green")
|
||||
else:
|
||||
# Do nothing since the .env file already exists. Not a great
|
||||
# idea to overwrite in case the user has added custom env vars set
|
||||
# in the .env file already.
|
||||
secho("➖ Skipped: .env. It already exists!", fg="yellow")
|
||||
|
||||
secho(
|
||||
f"🎉 Files generated successfully at path {save_path.parent}!",
|
||||
fg="cyan",
|
||||
bold=True,
|
||||
)
|
||||
|
||||
|
||||
@click.argument("path", required=False)
|
||||
@@ -385,13 +520,104 @@ def dockerfile(save_path: pathlib.Path, config: pathlib.Path):
|
||||
type=str,
|
||||
help=TEMPLATE_HELP_STRING,
|
||||
)
|
||||
@cli.command("new", help="Create a new LangGraph project from a template.")
|
||||
@cli.command("new", help="🌱 Create a new LangGraph project from a template.")
|
||||
@log_command
|
||||
def new(path: Optional[str], template: Optional[str]) -> None:
|
||||
"""Create a new LangGraph project from a template."""
|
||||
return create_new(path, template)
|
||||
|
||||
|
||||
@click.option(
|
||||
"--host",
|
||||
default="127.0.0.1",
|
||||
help="Network interface to bind the development server to. Default 127.0.0.1 is recommended for security. Only use 0.0.0.0 in trusted networks",
|
||||
)
|
||||
@click.option(
|
||||
"--port",
|
||||
default=2024,
|
||||
type=int,
|
||||
help="Port number to bind the development server to. Example: langgraph dev --port 8000",
|
||||
)
|
||||
@click.option(
|
||||
"--no-reload",
|
||||
is_flag=True,
|
||||
help="Disable automatic reloading when code changes are detected",
|
||||
)
|
||||
@click.option(
|
||||
"--config",
|
||||
type=click.Path(exists=True),
|
||||
default="langgraph.json",
|
||||
help="Path to configuration file declaring dependencies, graphs and environment variables",
|
||||
)
|
||||
@click.option(
|
||||
"--n-jobs-per-worker",
|
||||
default=None,
|
||||
type=int,
|
||||
help="Maximum number of concurrent jobs each worker process can handle. Default: 10",
|
||||
)
|
||||
@click.option(
|
||||
"--no-browser",
|
||||
is_flag=True,
|
||||
help="Skip automatically opening the browser when the server starts",
|
||||
)
|
||||
@click.option(
|
||||
"--debug-port",
|
||||
default=None,
|
||||
type=int,
|
||||
help="Enable remote debugging by listening on specified port. Requires debugpy to be installed",
|
||||
)
|
||||
@cli.command(
|
||||
"dev",
|
||||
help="🏃♀️➡️ Run LangGraph API server in development mode with hot reloading and debugging support",
|
||||
)
|
||||
@log_command
|
||||
def dev(
|
||||
host: str,
|
||||
port: int,
|
||||
no_reload: bool,
|
||||
config: str,
|
||||
n_jobs_per_worker: Optional[int],
|
||||
no_browser: bool,
|
||||
debug_port: Optional[int],
|
||||
):
|
||||
"""CLI entrypoint for running the LangGraph API server."""
|
||||
try:
|
||||
from langgraph_api.cli import run_server
|
||||
except ImportError:
|
||||
try:
|
||||
import pkg_resources
|
||||
|
||||
pkg_resources.require("langgraph-api-inmem")
|
||||
except (ImportError, pkg_resources.DistributionNotFound):
|
||||
raise click.UsageError(
|
||||
"Required package 'langgraph-api-inmem' is not installed.\n"
|
||||
"Please install it with:\n\n"
|
||||
' pip install -U "langgraph-cli[inmem]"\n\n'
|
||||
"If you're developing the langgraph-cli package locally, you can install in development mode:\n"
|
||||
" pip install -e ."
|
||||
) from None
|
||||
raise click.UsageError(
|
||||
"Could not import run_server. This likely means your installation is incomplete.\n"
|
||||
"Please ensure langgraph-cli is installed with the 'inmem' extra: pip install -U \"langgraph-cli[inmem]\""
|
||||
) from None
|
||||
|
||||
import json
|
||||
|
||||
with open(config, encoding="utf-8") as f:
|
||||
config_data = json.load(f)
|
||||
|
||||
graphs = config_data.get("graphs", {})
|
||||
run_server(
|
||||
host,
|
||||
port,
|
||||
not no_reload,
|
||||
graphs,
|
||||
n_jobs_per_worker=n_jobs_per_worker,
|
||||
open_browser=not no_browser,
|
||||
debug_port=debug_port,
|
||||
)
|
||||
|
||||
|
||||
def prepare_args_and_stdin(
|
||||
*,
|
||||
capabilities: DockerCapabilities,
|
||||
@@ -425,9 +651,11 @@ def prepare_args_and_stdin(
|
||||
config_path,
|
||||
config,
|
||||
watch=watch,
|
||||
base_image="langchain/langgraphjs-api"
|
||||
if config.get("node_version")
|
||||
else "langchain/langgraph-api",
|
||||
base_image=(
|
||||
"langchain/langgraphjs-api"
|
||||
if config.get("node_version")
|
||||
else "langchain/langgraph-api"
|
||||
),
|
||||
)
|
||||
return args, stdin
|
||||
|
||||
@@ -454,9 +682,11 @@ def prepare(
|
||||
subp_exec(
|
||||
"docker",
|
||||
"pull",
|
||||
f"langchain/langgraphjs-api:{config['node_version']}"
|
||||
if config.get("node_version")
|
||||
else f"langchain/langgraph-api:{config['python_version']}",
|
||||
(
|
||||
f"langchain/langgraphjs-api:{config['node_version']}"
|
||||
if config.get("node_version")
|
||||
else f"langchain/langgraph-api:{config['python_version']}"
|
||||
),
|
||||
verbose=verbose,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -12,34 +12,6 @@ DEFAULT_POSTGRES_URI = (
|
||||
"postgres://postgres:postgres@langgraph-postgres:5432/postgres?sslmode=disable"
|
||||
)
|
||||
|
||||
REDIS = """
|
||||
langgraph-redis:
|
||||
image: redis:6
|
||||
healthcheck:
|
||||
test: redis-cli ping
|
||||
interval: 5s
|
||||
timeout: 1s
|
||||
retries: 5
|
||||
"""
|
||||
|
||||
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
|
||||
"""
|
||||
|
||||
|
||||
class Version(NamedTuple):
|
||||
major: int
|
||||
@@ -116,28 +88,149 @@ def check_capabilities(runner) -> DockerCapabilities:
|
||||
|
||||
def debugger_compose(
|
||||
*, port: Optional[int] = None, base_url: Optional[str] = None
|
||||
) -> str:
|
||||
) -> dict:
|
||||
if port is None:
|
||||
return ""
|
||||
|
||||
compose_str = """
|
||||
langgraph-debugger:
|
||||
image: langchain/langgraph-debugger
|
||||
restart: on-failure
|
||||
depends_on:
|
||||
langgraph-postgres:
|
||||
condition: service_healthy
|
||||
ports:
|
||||
- "{port}:3968"
|
||||
"""
|
||||
config = {
|
||||
"langgraph-debugger": {
|
||||
"image": "langchain/langgraph-debugger",
|
||||
"restart": "on-failure",
|
||||
"depends_on": {
|
||||
"langgraph-postgres": {"condition": "service_healthy"},
|
||||
},
|
||||
"ports": [f'"{port}:3968"'],
|
||||
}
|
||||
}
|
||||
|
||||
if base_url:
|
||||
compose_str += """
|
||||
environment:
|
||||
VITE_STUDIO_LOCAL_GRAPH_URL: {base_url}
|
||||
"""
|
||||
config["langgraph-debugger"]["environment"] = {
|
||||
"VITE_STUDIO_LOCAL_GRAPH_URL": base_url
|
||||
}
|
||||
|
||||
return compose_str.format(port=port, base_url=base_url)
|
||||
return config
|
||||
|
||||
|
||||
# Function to convert dictionary to YAML
|
||||
def dict_to_yaml(d: dict, *, indent: int = 0) -> str:
|
||||
"""Convert a dictionary to a YAML string."""
|
||||
yaml_str = ""
|
||||
|
||||
for idx, (key, value) in enumerate(d.items()):
|
||||
# Format things in a visually appealing way
|
||||
# Use an extra newline for top-level keys only
|
||||
if idx >= 1 and indent < 2:
|
||||
yaml_str += "\n"
|
||||
space = " " * indent
|
||||
if isinstance(value, dict):
|
||||
yaml_str += f"{space}{key}:\n" + dict_to_yaml(value, indent=indent + 1)
|
||||
elif isinstance(value, list):
|
||||
yaml_str += f"{space}{key}:\n"
|
||||
for item in value:
|
||||
yaml_str += f"{space} - {item}\n"
|
||||
else:
|
||||
yaml_str += f"{space}{key}: {value}\n"
|
||||
return yaml_str
|
||||
|
||||
|
||||
def compose_as_dict(
|
||||
capabilities: DockerCapabilities,
|
||||
*,
|
||||
port: int,
|
||||
debugger_port: Optional[int] = None,
|
||||
debugger_base_url: Optional[str] = None,
|
||||
# postgres://user:password@host:port/database?option=value
|
||||
postgres_uri: Optional[str] = None,
|
||||
) -> dict:
|
||||
"""Create a docker compose file as a dictionary in YML style."""
|
||||
if postgres_uri is None:
|
||||
include_db = True
|
||||
postgres_uri = DEFAULT_POSTGRES_URI
|
||||
else:
|
||||
include_db = False
|
||||
|
||||
# The services below are defined in a non-intuitive order to match
|
||||
# the existing unit tests for this function.
|
||||
# It's fine to re-order just requires updating the unit tests, so it should
|
||||
# be done with caution.
|
||||
|
||||
# Define the Redis service first as per the test order
|
||||
services = {
|
||||
"langgraph-redis": {
|
||||
"image": "redis:6",
|
||||
"healthcheck": {
|
||||
"test": "redis-cli ping",
|
||||
"interval": "5s",
|
||||
"timeout": "1s",
|
||||
"retries": 5,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
# Add Postgres service before langgraph-api if it is needed
|
||||
if include_db:
|
||||
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,
|
||||
},
|
||||
}
|
||||
if capabilities.healthcheck_start_interval:
|
||||
services["langgraph-postgres"]["healthcheck"]["interval"] = "60s"
|
||||
services["langgraph-postgres"]["healthcheck"]["start_interval"] = "1s"
|
||||
else:
|
||||
services["langgraph-postgres"]["healthcheck"]["interval"] = "5s"
|
||||
|
||||
# Add optional debugger service if debugger_port is specified
|
||||
if debugger_port:
|
||||
services["langgraph-debugger"] = debugger_compose(
|
||||
port=debugger_port, base_url=debugger_base_url
|
||||
)["langgraph-debugger"]
|
||||
|
||||
# Add langgraph-api service
|
||||
services["langgraph-api"] = {
|
||||
"ports": [f'"{port}:8000"'],
|
||||
"depends_on": {
|
||||
"langgraph-redis": {"condition": "service_healthy"},
|
||||
},
|
||||
"environment": {
|
||||
"REDIS_URI": "redis://langgraph-redis:6379",
|
||||
"POSTGRES_URI": postgres_uri,
|
||||
},
|
||||
}
|
||||
|
||||
# If Postgres is included, add it to the dependencies of langgraph-api
|
||||
if include_db:
|
||||
services["langgraph-api"]["depends_on"]["langgraph-postgres"] = {
|
||||
"condition": "service_healthy"
|
||||
}
|
||||
|
||||
# Additional healthcheck for langgraph-api if required
|
||||
if capabilities.healthcheck_start_interval:
|
||||
services["langgraph-api"]["healthcheck"] = {
|
||||
"test": "python /api/healthcheck.py",
|
||||
"interval": "60s",
|
||||
"start_interval": "1s",
|
||||
"start_period": "10s",
|
||||
}
|
||||
|
||||
# Final compose dictionary with volumes included if needed
|
||||
compose_dict = {}
|
||||
if include_db:
|
||||
compose_dict["volumes"] = {"langgraph-data": {"driver": "local"}}
|
||||
compose_dict["services"] = services
|
||||
|
||||
return compose_dict
|
||||
|
||||
|
||||
def compose(
|
||||
@@ -149,54 +242,13 @@ def compose(
|
||||
# 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 ""
|
||||
"""Create a docker compose file as a string."""
|
||||
compose_content = compose_as_dict(
|
||||
capabilities,
|
||||
port=port,
|
||||
debugger_port=debugger_port,
|
||||
debugger_base_url=debugger_base_url,
|
||||
postgres_uri=postgres_uri,
|
||||
)
|
||||
if db:
|
||||
if capabilities.healthcheck_start_interval:
|
||||
db += """
|
||||
interval: 60s
|
||||
start_interval: 1s"""
|
||||
else:
|
||||
db += """
|
||||
interval: 5s"""
|
||||
|
||||
compose_str = f"""{volumes}services:
|
||||
{REDIS}
|
||||
{db}
|
||||
{debugger_compose(port=debugger_port, base_url=debugger_base_url)}
|
||||
langgraph-api:
|
||||
ports:
|
||||
- "{port}:8000\"
|
||||
depends_on:
|
||||
langgraph-redis:
|
||||
condition: service_healthy"""
|
||||
if include_db:
|
||||
compose_str += """
|
||||
langgraph-postgres:
|
||||
condition: service_healthy"""
|
||||
compose_str += f"""
|
||||
environment:
|
||||
REDIS_URI: redis://langgraph-redis:6379
|
||||
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"""
|
||||
|
||||
compose_str = dict_to_yaml(compose_content)
|
||||
return compose_str
|
||||
@@ -85,7 +85,7 @@ def _choose_template() -> str:
|
||||
fg="green",
|
||||
)
|
||||
version_choice: int = click.prompt(
|
||||
"Choose version (1 for Python 🐍, 2 for JS/TS 🌐)", type=int
|
||||
"Choose language (1 for Python 🐍, 2 for JS/TS 🌐)", type=int
|
||||
)
|
||||
|
||||
if version_choice == 1:
|
||||
|
||||
Generated
+1303
-111
File diff suppressed because it is too large.
Load diff
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "langgraph-cli"
|
||||
version = "0.1.53"
|
||||
version = "0.1.55"
|
||||
description = "CLI for interacting with LangGraph API"
|
||||
authors = []
|
||||
license = "MIT"
|
||||
@@ -14,6 +14,7 @@ langgraph = "langgraph_cli.cli:cli"
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.9.0,<4.0"
|
||||
click = "^8.1.7"
|
||||
langgraph-api-inmem = { version = ">=0.0.5,<0.1.0", optional = true, python = ">=3.11" }
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
ruff = "^0.6.2"
|
||||
@@ -24,6 +25,9 @@ pytest-mock = "^3.11.1"
|
||||
pytest-watch = "^4.2.0"
|
||||
mypy = "^1.10.0"
|
||||
|
||||
[tool.poetry.extras]
|
||||
inmem = ["langgraph-api-inmem"]
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
# --strict-markers will raise errors on unknown marks.
|
||||
# https://docs.pytest.org/en/7.1.x/how-to/mark.html#raising-errors-on-unknown-marks
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
import json
|
||||
import pathlib
|
||||
import shutil
|
||||
import tempfile
|
||||
from contextlib import contextmanager
|
||||
from pathlib import Path
|
||||
|
||||
from click.testing import CliRunner
|
||||
|
||||
@@ -14,7 +19,26 @@ DEFAULT_DOCKER_CAPABILITIES = DockerCapabilities(
|
||||
)
|
||||
|
||||
|
||||
def test_prepare_args_and_stdin():
|
||||
@contextmanager
|
||||
def temporary_config_folder(config_content: dict):
|
||||
# Create a temporary directory
|
||||
temp_dir = tempfile.mkdtemp()
|
||||
try:
|
||||
# Define the path for the config.json file
|
||||
config_path = Path(temp_dir) / "config.json"
|
||||
|
||||
# Write the provided dictionary content to config.json
|
||||
with open(config_path, "w", encoding="utf-8") as config_file:
|
||||
json.dump(config_content, config_file)
|
||||
|
||||
# Yield the temporary directory path for use within the context
|
||||
yield config_path.parent
|
||||
finally:
|
||||
# Cleanup the temporary directory and its contents
|
||||
shutil.rmtree(temp_dir)
|
||||
|
||||
|
||||
def test_prepare_args_and_stdin() -> None:
|
||||
# this basically serves as an end-to-end test for using config and docker helpers
|
||||
config_path = pathlib.Path("./langgraph.json")
|
||||
config = validate_config(
|
||||
@@ -28,7 +52,7 @@ def test_prepare_args_and_stdin():
|
||||
capabilities=DEFAULT_DOCKER_CAPABILITIES,
|
||||
config_path=config_path,
|
||||
config=config,
|
||||
docker_compose="custom-docker-compose.yml",
|
||||
docker_compose=pathlib.Path("custom-docker-compose.yml"),
|
||||
port=port,
|
||||
debugger_port=debugger_port,
|
||||
debugger_base_url=debugger_graph_url,
|
||||
@@ -131,3 +155,90 @@ def test_version_option() -> None:
|
||||
assert (
|
||||
"LangGraph CLI, version" in result.output
|
||||
), "Expected version information in output"
|
||||
|
||||
|
||||
def test_dockerfile_command_basic() -> None:
|
||||
"""Test the 'dockerfile' command with basic configuration."""
|
||||
runner = CliRunner()
|
||||
config_content = {
|
||||
"node_version": "20", # Add any other necessary configuration fields
|
||||
"graphs": {"agent": "agent.py:graph"},
|
||||
}
|
||||
|
||||
with temporary_config_folder(config_content) as temp_dir:
|
||||
save_path = temp_dir / "Dockerfile"
|
||||
|
||||
result = runner.invoke(
|
||||
cli,
|
||||
["dockerfile", str(save_path), "--config", str(temp_dir / "config.json")],
|
||||
)
|
||||
|
||||
# Assert command was successful
|
||||
assert result.exit_code == 0, result.output
|
||||
assert "✅ Created: Dockerfile" in result.output
|
||||
|
||||
# Check if Dockerfile was created
|
||||
assert save_path.exists()
|
||||
|
||||
|
||||
def test_dockerfile_command_with_docker_compose() -> None:
|
||||
"""Test the 'dockerfile' command with Docker Compose configuration."""
|
||||
runner = CliRunner()
|
||||
config_content = {
|
||||
"dependencies": ["./my_agent"],
|
||||
"graphs": {"agent": "./my_agent/agent.py:graph"},
|
||||
"env": ".env",
|
||||
}
|
||||
with temporary_config_folder(config_content) as temp_dir:
|
||||
save_path = temp_dir / "Dockerfile"
|
||||
# Add agent.py file
|
||||
agent_path = temp_dir / "my_agent" / "agent.py"
|
||||
agent_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
agent_path.touch()
|
||||
|
||||
result = runner.invoke(
|
||||
cli,
|
||||
[
|
||||
"dockerfile",
|
||||
str(save_path),
|
||||
"--config",
|
||||
str(temp_dir / "config.json"),
|
||||
"--add-docker-compose",
|
||||
],
|
||||
)
|
||||
|
||||
# Assert command was successful
|
||||
assert result.exit_code == 0
|
||||
assert "✅ Created: Dockerfile" in result.output
|
||||
assert "✅ Created: .dockerignore" in result.output
|
||||
assert "✅ Created: docker-compose.yml" in result.output
|
||||
assert (
|
||||
"✅ Created: .env" in result.output or "➖ Skipped: .env" in result.output
|
||||
)
|
||||
assert "🎉 Files generated successfully" in result.output
|
||||
|
||||
# Check if Dockerfile, .dockerignore, docker-compose.yml, and .env were created
|
||||
assert save_path.exists()
|
||||
assert (temp_dir / ".dockerignore").exists()
|
||||
assert (temp_dir / "docker-compose.yml").exists()
|
||||
assert (temp_dir / ".env").exists() or "➖ Skipped: .env" in result.output
|
||||
|
||||
|
||||
def test_dockerfile_command_with_bad_config() -> None:
|
||||
"""Test the 'dockerfile' command with basic configuration."""
|
||||
runner = CliRunner()
|
||||
config_content = {
|
||||
"node_version": "20" # Add any other necessary configuration fields
|
||||
}
|
||||
|
||||
with temporary_config_folder(config_content) as temp_dir:
|
||||
save_path = temp_dir / "Dockerfile"
|
||||
|
||||
result = runner.invoke(
|
||||
cli,
|
||||
["dockerfile", str(save_path), "--config", str(temp_dir / "conf.json")],
|
||||
)
|
||||
|
||||
# Assert command was successful
|
||||
assert result.exit_code == 2
|
||||
assert "conf.json' does not exist" in result.output
|
||||
@@ -49,7 +49,7 @@ test:
|
||||
exit $$EXIT_CODE
|
||||
|
||||
test_watch:
|
||||
make start-postgres && poetry run ptw . -- --ff -v -x -n auto --dist worksteal --snapshot-update --tb short $(TEST); \
|
||||
make start-postgres && poetry run ptw . -- --ff -vv -x -n auto --dist worksteal --snapshot-update --tb short $(TEST); \
|
||||
EXIT_CODE=$$?; \
|
||||
make stop-postgres; \
|
||||
exit $$EXIT_CODE
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import sys
|
||||
from os import getenv
|
||||
from types import MappingProxyType
|
||||
from typing import Any, Literal, Mapping, cast
|
||||
|
||||
@@ -10,6 +11,7 @@ from langgraph.types import Interrupt, Send # noqa: F401
|
||||
# --- Empty read-only containers ---
|
||||
EMPTY_MAP: Mapping[str, Any] = MappingProxyType({})
|
||||
EMPTY_SEQ: tuple[str, ...] = tuple()
|
||||
MISSING = object()
|
||||
|
||||
# --- Public constants ---
|
||||
TAG_NOSTREAM = sys.intern("langsmith:nostream")
|
||||
@@ -28,6 +30,8 @@ INPUT = sys.intern("__input__")
|
||||
# for values passed as input to the graph
|
||||
INTERRUPT = sys.intern("__interrupt__")
|
||||
# for dynamic interrupts raised by nodes
|
||||
RESUME = sys.intern("__resume__")
|
||||
# for values passed to resume a node after an interrupt
|
||||
ERROR = sys.intern("__error__")
|
||||
# for errors raised by nodes
|
||||
NO_WRITES = sys.intern("__no_writes__")
|
||||
@@ -69,6 +73,8 @@ CONFIG_KEY_CHECKPOINT_NS = sys.intern("checkpoint_ns")
|
||||
# holds the current checkpoint_ns, "" for root graph
|
||||
CONFIG_KEY_NODE_FINISHED = sys.intern("__pregel_node_finished")
|
||||
# callback to be called when a node is finished
|
||||
CONFIG_KEY_RESUME_VALUE = sys.intern("__pregel_resume_value")
|
||||
# holds the value that "answers" an interrupt() call
|
||||
|
||||
# --- Other constants ---
|
||||
PUSH = sys.intern("__pregel_push")
|
||||
@@ -81,12 +87,17 @@ NS_END = sys.intern(":")
|
||||
# for checkpoint_ns, for each level, separates the namespace from the task_id
|
||||
CONF = cast(Literal["configurable"], sys.intern("configurable"))
|
||||
# key for the configurable dict in RunnableConfig
|
||||
FF_SEND_V2 = getenv("LANGGRAPH_FF_SEND_V2", "false").lower() == "true"
|
||||
# temporary flag to enable new Send semantics
|
||||
NULL_TASK_ID = sys.intern("00000000-0000-0000-0000-000000000000")
|
||||
# the task_id to use for writes that are not associated with a task
|
||||
|
||||
RESERVED = {
|
||||
TAG_HIDDEN,
|
||||
# reserved write keys
|
||||
INPUT,
|
||||
INTERRUPT,
|
||||
RESUME,
|
||||
ERROR,
|
||||
NO_WRITES,
|
||||
SCHEDULED,
|
||||
|
||||
@@ -70,7 +70,7 @@ class NodeInterrupt(GraphInterrupt):
|
||||
"""Raised by a node to interrupt execution."""
|
||||
|
||||
def __init__(self, value: Any) -> None:
|
||||
super().__init__([Interrupt(value)])
|
||||
super().__init__([Interrupt(value=value)])
|
||||
|
||||
|
||||
class GraphDelegate(Exception):
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import dataclasses
|
||||
import inspect
|
||||
import logging
|
||||
import typing
|
||||
@@ -14,7 +15,6 @@ from typing import (
|
||||
Optional,
|
||||
Sequence,
|
||||
Type,
|
||||
TypeVar,
|
||||
Union,
|
||||
cast,
|
||||
get_args,
|
||||
@@ -50,15 +50,13 @@ from langgraph.managed.base import (
|
||||
from langgraph.pregel.read import ChannelRead, PregelNode
|
||||
from langgraph.pregel.write import SKIP_WRITE, ChannelWrite, ChannelWriteEntry
|
||||
from langgraph.store.base import BaseStore
|
||||
from langgraph.types import All, Checkpointer, Command, RetryPolicy
|
||||
from langgraph.types import _DC_KWARGS, All, Checkpointer, Command, N, RetryPolicy
|
||||
from langgraph.utils.fields import get_field_default
|
||||
from langgraph.utils.pydantic import create_model
|
||||
from langgraph.utils.runnable import RunnableCallable, coerce_to_runnable
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
N = TypeVar("N")
|
||||
|
||||
|
||||
def _warn_invalid_state_schema(schema: Union[Type[Any], Any]) -> None:
|
||||
if isinstance(schema, type):
|
||||
@@ -81,20 +79,20 @@ def _get_node_name(node: RunnableLike) -> str:
|
||||
raise TypeError(f"Unsupported node type: {type(node)}")
|
||||
|
||||
|
||||
class GraphCommand(Command, Generic[N]):
|
||||
@dataclasses.dataclass(**_DC_KWARGS)
|
||||
class GraphCommand(Generic[N], Command[N]):
|
||||
"""One or more commands to update a StateGraph's state and go to, or send messages to nodes."""
|
||||
|
||||
__slots__ = ("goto",)
|
||||
goto: Union[str, Sequence[str]] = ()
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
update: Optional[dict[str, Any]] = None,
|
||||
goto: Union[str, Sequence[str]] = (),
|
||||
send: Union[Send, Sequence[Send]] = (),
|
||||
) -> None:
|
||||
super().__init__(update=update, send=send)
|
||||
self.goto = goto
|
||||
def __repr__(self) -> str:
|
||||
# get all non-None values
|
||||
contents = ", ".join(
|
||||
f"{key}={value!r}"
|
||||
for key, value in dataclasses.asdict(self).items()
|
||||
if value
|
||||
)
|
||||
return f"Command({contents})"
|
||||
|
||||
|
||||
class StateNodeSpec(NamedTuple):
|
||||
@@ -389,7 +387,7 @@ class StateGraph(Graph):
|
||||
input = input_hint
|
||||
if (
|
||||
(rtn := hints.get("return"))
|
||||
and get_origin(rtn) is GraphCommand
|
||||
and get_origin(rtn) in (Command, GraphCommand)
|
||||
and (rargs := get_args(rtn))
|
||||
and get_origin(rargs[0]) is Literal
|
||||
and (vals := get_args(rargs[0]))
|
||||
|
||||
@@ -65,10 +65,14 @@ from langgraph.constants import (
|
||||
CONFIG_KEY_STREAM,
|
||||
CONFIG_KEY_STREAM_WRITER,
|
||||
CONFIG_KEY_TASK_ID,
|
||||
END,
|
||||
ERROR,
|
||||
INPUT,
|
||||
INTERRUPT,
|
||||
NS_END,
|
||||
NS_SEP,
|
||||
NULL_TASK_ID,
|
||||
PUSH,
|
||||
SCHEDULED,
|
||||
)
|
||||
from langgraph.errors import (
|
||||
@@ -98,7 +102,13 @@ from langgraph.pregel.utils import find_subgraph_pregel, get_new_channel_version
|
||||
from langgraph.pregel.validate import validate_graph, validate_keys
|
||||
from langgraph.pregel.write import ChannelWrite, ChannelWriteEntry
|
||||
from langgraph.store.base import BaseStore
|
||||
from langgraph.types import All, Checkpointer, LoopProtocol, StateSnapshot, StreamMode
|
||||
from langgraph.types import (
|
||||
All,
|
||||
Checkpointer,
|
||||
LoopProtocol,
|
||||
StateSnapshot,
|
||||
StreamMode,
|
||||
)
|
||||
from langgraph.utils.config import (
|
||||
ensure_config,
|
||||
merge_configs,
|
||||
@@ -468,6 +478,7 @@ class Pregel(PregelProtocol):
|
||||
# tasks for this checkpoint
|
||||
next_tasks = prepare_next_tasks(
|
||||
saved.checkpoint,
|
||||
saved.pending_writes or [],
|
||||
self.nodes,
|
||||
channels,
|
||||
managed,
|
||||
@@ -511,6 +522,15 @@ class Pregel(PregelProtocol):
|
||||
config, subgraphs=True
|
||||
)
|
||||
# apply pending writes
|
||||
if null_writes := [
|
||||
w[1:] for w in saved.pending_writes or [] if w[0] == NULL_TASK_ID
|
||||
]:
|
||||
apply_writes(
|
||||
saved.checkpoint,
|
||||
channels,
|
||||
[PregelTaskWrites((), INPUT, null_writes, [])],
|
||||
None,
|
||||
)
|
||||
if apply_pending_writes and saved.pending_writes:
|
||||
for tid, k, v in saved.pending_writes:
|
||||
if k in (ERROR, INTERRUPT, SCHEDULED):
|
||||
@@ -570,6 +590,7 @@ class Pregel(PregelProtocol):
|
||||
# tasks for this checkpoint
|
||||
next_tasks = prepare_next_tasks(
|
||||
saved.checkpoint,
|
||||
saved.pending_writes or [],
|
||||
self.nodes,
|
||||
channels,
|
||||
managed,
|
||||
@@ -613,6 +634,15 @@ class Pregel(PregelProtocol):
|
||||
config, subgraphs=True
|
||||
)
|
||||
# apply pending writes
|
||||
if null_writes := [
|
||||
w[1:] for w in saved.pending_writes or [] if w[0] == NULL_TASK_ID
|
||||
]:
|
||||
apply_writes(
|
||||
saved.checkpoint,
|
||||
channels,
|
||||
[PregelTaskWrites((), INPUT, null_writes, [])],
|
||||
None,
|
||||
)
|
||||
if apply_pending_writes and saved.pending_writes:
|
||||
for tid, k, v in saved.pending_writes:
|
||||
if k in (ERROR, INTERRUPT, SCHEDULED):
|
||||
@@ -872,12 +902,13 @@ class Pregel(PregelProtocol):
|
||||
checkpoint,
|
||||
LoopProtocol(config=config, step=step + 1, stop=step + 2),
|
||||
) as (channels, managed):
|
||||
# no values, just clear all tasks
|
||||
if values is None and as_node is None:
|
||||
# no values as END, just clear all tasks
|
||||
if values is None and as_node == END:
|
||||
if saved is not None:
|
||||
# tasks for this checkpoint
|
||||
next_tasks = prepare_next_tasks(
|
||||
checkpoint,
|
||||
saved.pending_writes or [],
|
||||
self.nodes,
|
||||
channels,
|
||||
managed,
|
||||
@@ -888,6 +919,18 @@ class Pregel(PregelProtocol):
|
||||
checkpointer=self.checkpointer or None,
|
||||
manager=None,
|
||||
)
|
||||
# apply null writes
|
||||
if null_writes := [
|
||||
w[1:]
|
||||
for w in saved.pending_writes or []
|
||||
if w[0] == NULL_TASK_ID
|
||||
]:
|
||||
apply_writes(
|
||||
saved.checkpoint,
|
||||
channels,
|
||||
[PregelTaskWrites((), INPUT, null_writes, [])],
|
||||
None,
|
||||
)
|
||||
# apply writes from tasks that already ran
|
||||
for tid, k, v in saved.pending_writes or []:
|
||||
if k in (ERROR, INTERRUPT, SCHEDULED):
|
||||
@@ -913,6 +956,42 @@ class Pregel(PregelProtocol):
|
||||
return patch_checkpoint_map(
|
||||
next_config, saved.metadata if saved else None
|
||||
)
|
||||
# no values, copy checkpoint
|
||||
if values is None and as_node is None:
|
||||
next_checkpoint = create_checkpoint(checkpoint, None, step)
|
||||
# copy checkpoint
|
||||
next_config = checkpointer.put(
|
||||
checkpoint_config,
|
||||
next_checkpoint,
|
||||
{
|
||||
**checkpoint_metadata,
|
||||
"source": "update",
|
||||
"step": step + 1,
|
||||
"writes": {},
|
||||
"parents": saved.metadata.get("parents", {}) if saved else {},
|
||||
},
|
||||
{},
|
||||
)
|
||||
return patch_checkpoint_map(
|
||||
next_config, saved.metadata if saved else None
|
||||
)
|
||||
if values is None and as_node == "__copy__":
|
||||
next_checkpoint = create_checkpoint(checkpoint, None, step)
|
||||
# copy checkpoint
|
||||
next_config = checkpointer.put(
|
||||
saved.parent_config or saved.config if saved else checkpoint_config,
|
||||
next_checkpoint,
|
||||
{
|
||||
**checkpoint_metadata,
|
||||
"source": "fork",
|
||||
"step": step + 1,
|
||||
"parents": saved.metadata.get("parents", {}) if saved else {},
|
||||
},
|
||||
{},
|
||||
)
|
||||
return patch_checkpoint_map(
|
||||
next_config, saved.metadata if saved else None
|
||||
)
|
||||
# apply pending writes, if not on specific checkpoint
|
||||
if (
|
||||
CONFIG_KEY_CHECKPOINT_ID not in config[CONF]
|
||||
@@ -922,6 +1001,7 @@ class Pregel(PregelProtocol):
|
||||
# tasks for this checkpoint
|
||||
next_tasks = prepare_next_tasks(
|
||||
checkpoint,
|
||||
saved.pending_writes,
|
||||
self.nodes,
|
||||
channels,
|
||||
managed,
|
||||
@@ -932,6 +1012,16 @@ class Pregel(PregelProtocol):
|
||||
checkpointer=self.checkpointer or None,
|
||||
manager=None,
|
||||
)
|
||||
# apply null writes
|
||||
if null_writes := [
|
||||
w[1:] for w in saved.pending_writes or [] if w[0] == NULL_TASK_ID
|
||||
]:
|
||||
apply_writes(
|
||||
saved.checkpoint,
|
||||
channels,
|
||||
[PregelTaskWrites((), INPUT, null_writes, [])],
|
||||
None,
|
||||
)
|
||||
# apply writes
|
||||
for tid, k, v in saved.pending_writes:
|
||||
if k in (ERROR, INTERRUPT, SCHEDULED):
|
||||
@@ -1001,8 +1091,14 @@ class Pregel(PregelProtocol):
|
||||
),
|
||||
)
|
||||
# save task writes
|
||||
if saved:
|
||||
checkpointer.put_writes(checkpoint_config, task.writes, task_id)
|
||||
# channel writes are saved to current checkpoint
|
||||
# push writes are saved to next checkpoint
|
||||
channel_writes, push_writes = (
|
||||
[w for w in task.writes if w[0] != PUSH],
|
||||
[w for w in task.writes if w[0] == PUSH],
|
||||
)
|
||||
if saved and channel_writes:
|
||||
checkpointer.put_writes(checkpoint_config, channel_writes, task_id)
|
||||
# apply to checkpoint and save
|
||||
mv_writes = apply_writes(
|
||||
checkpoint, channels, [task], checkpointer.get_next_version
|
||||
@@ -1023,6 +1119,8 @@ class Pregel(PregelProtocol):
|
||||
checkpoint_previous_versions, checkpoint["channel_versions"]
|
||||
),
|
||||
)
|
||||
if push_writes:
|
||||
checkpointer.put_writes(next_config, push_writes, task_id)
|
||||
return patch_checkpoint_map(next_config, saved.metadata if saved else None)
|
||||
|
||||
async def aupdate_state(
|
||||
@@ -1083,11 +1181,12 @@ class Pregel(PregelProtocol):
|
||||
managed,
|
||||
):
|
||||
# no values, just clear all tasks
|
||||
if values is None and as_node is None:
|
||||
if values is None and as_node == END:
|
||||
if saved is not None:
|
||||
# tasks for this checkpoint
|
||||
next_tasks = prepare_next_tasks(
|
||||
checkpoint,
|
||||
saved.pending_writes or [],
|
||||
self.nodes,
|
||||
channels,
|
||||
managed,
|
||||
@@ -1098,6 +1197,18 @@ class Pregel(PregelProtocol):
|
||||
checkpointer=self.checkpointer or None,
|
||||
manager=None,
|
||||
)
|
||||
# apply null writes
|
||||
if null_writes := [
|
||||
w[1:]
|
||||
for w in saved.pending_writes or []
|
||||
if w[0] == NULL_TASK_ID
|
||||
]:
|
||||
apply_writes(
|
||||
saved.checkpoint,
|
||||
channels,
|
||||
[PregelTaskWrites((), INPUT, null_writes, [])],
|
||||
None,
|
||||
)
|
||||
# apply writes from tasks that already ran
|
||||
for tid, k, v in saved.pending_writes or []:
|
||||
if k in (ERROR, INTERRUPT, SCHEDULED):
|
||||
@@ -1123,6 +1234,42 @@ class Pregel(PregelProtocol):
|
||||
return patch_checkpoint_map(
|
||||
next_config, saved.metadata if saved else None
|
||||
)
|
||||
# no values, copy checkpoint
|
||||
if values is None and as_node is None:
|
||||
next_checkpoint = create_checkpoint(checkpoint, None, step)
|
||||
# copy checkpoint
|
||||
next_config = await checkpointer.aput(
|
||||
checkpoint_config,
|
||||
next_checkpoint,
|
||||
{
|
||||
**checkpoint_metadata,
|
||||
"source": "update",
|
||||
"step": step + 1,
|
||||
"writes": {},
|
||||
"parents": saved.metadata.get("parents", {}) if saved else {},
|
||||
},
|
||||
{},
|
||||
)
|
||||
return patch_checkpoint_map(
|
||||
next_config, saved.metadata if saved else None
|
||||
)
|
||||
if values is None and as_node == "__copy__":
|
||||
next_checkpoint = create_checkpoint(checkpoint, None, step)
|
||||
# copy checkpoint
|
||||
next_config = await checkpointer.aput(
|
||||
saved.parent_config or saved.config if saved else checkpoint_config,
|
||||
next_checkpoint,
|
||||
{
|
||||
**checkpoint_metadata,
|
||||
"source": "fork",
|
||||
"step": step + 1,
|
||||
"parents": saved.metadata.get("parents", {}) if saved else {},
|
||||
},
|
||||
{},
|
||||
)
|
||||
return patch_checkpoint_map(
|
||||
next_config, saved.metadata if saved else None
|
||||
)
|
||||
# apply pending writes, if not on specific checkpoint
|
||||
if (
|
||||
CONFIG_KEY_CHECKPOINT_ID not in config[CONF]
|
||||
@@ -1132,6 +1279,7 @@ class Pregel(PregelProtocol):
|
||||
# tasks for this checkpoint
|
||||
next_tasks = prepare_next_tasks(
|
||||
checkpoint,
|
||||
saved.pending_writes,
|
||||
self.nodes,
|
||||
channels,
|
||||
managed,
|
||||
@@ -1142,6 +1290,16 @@ class Pregel(PregelProtocol):
|
||||
checkpointer=self.checkpointer or None,
|
||||
manager=None,
|
||||
)
|
||||
# apply null writes
|
||||
if null_writes := [
|
||||
w[1:] for w in saved.pending_writes or [] if w[0] == NULL_TASK_ID
|
||||
]:
|
||||
apply_writes(
|
||||
saved.checkpoint,
|
||||
channels,
|
||||
[PregelTaskWrites((), INPUT, null_writes, [])],
|
||||
None,
|
||||
)
|
||||
for tid, k, v in saved.pending_writes:
|
||||
if k in (ERROR, INTERRUPT, SCHEDULED):
|
||||
continue
|
||||
@@ -1208,14 +1366,23 @@ class Pregel(PregelProtocol):
|
||||
),
|
||||
)
|
||||
# save task writes
|
||||
if saved:
|
||||
await checkpointer.aput_writes(checkpoint_config, writes, task_id)
|
||||
# channel writes are saved to current checkpoint
|
||||
# push writes are saved to next checkpoint
|
||||
channel_writes, push_writes = (
|
||||
[w for w in task.writes if w[0] != PUSH],
|
||||
[w for w in task.writes if w[0] == PUSH],
|
||||
)
|
||||
if saved and channel_writes:
|
||||
await checkpointer.aput_writes(
|
||||
checkpoint_config, channel_writes, task_id
|
||||
)
|
||||
# apply to checkpoint and save
|
||||
mv_writes = apply_writes(
|
||||
checkpoint, channels, [task], checkpointer.get_next_version
|
||||
)
|
||||
assert not mv_writes, "Can't write to SharedValues from update_state"
|
||||
checkpoint = create_checkpoint(checkpoint, channels, step + 1)
|
||||
# save checkpoint, after applying writes
|
||||
next_config = await checkpointer.aput(
|
||||
checkpoint_config,
|
||||
checkpoint,
|
||||
@@ -1230,6 +1397,9 @@ class Pregel(PregelProtocol):
|
||||
checkpoint_previous_versions, checkpoint["channel_versions"]
|
||||
),
|
||||
)
|
||||
# save push writes
|
||||
if push_writes:
|
||||
await checkpointer.aput_writes(next_config, push_writes, task_id)
|
||||
return patch_checkpoint_map(next_config, saved.metadata if saved else None)
|
||||
|
||||
def _defaults(
|
||||
@@ -1432,12 +1602,16 @@ class Pregel(PregelProtocol):
|
||||
specs=self.channels,
|
||||
output_keys=output_keys,
|
||||
stream_keys=self.stream_channels_asis,
|
||||
interrupt_before=interrupt_before_,
|
||||
interrupt_after=interrupt_after_,
|
||||
manager=run_manager,
|
||||
debug=debug,
|
||||
) as loop:
|
||||
# create runner
|
||||
runner = PregelRunner(
|
||||
submit=loop.submit,
|
||||
put_writes=loop.put_writes,
|
||||
schedule_task=loop.accept_push,
|
||||
node_finished=config[CONF].get(CONFIG_KEY_NODE_FINISHED),
|
||||
)
|
||||
# enable subgraph streaming
|
||||
@@ -1468,12 +1642,7 @@ class Pregel(PregelProtocol):
|
||||
# channel updates from step N are only visible in step N+1
|
||||
# channels are guaranteed to be immutable for the duration of the step,
|
||||
# with channel updates applied only at the transition between steps
|
||||
while loop.tick(
|
||||
input_keys=self.input_channels,
|
||||
interrupt_before=interrupt_before_,
|
||||
interrupt_after=interrupt_after_,
|
||||
manager=run_manager,
|
||||
):
|
||||
while loop.tick(input_keys=self.input_channels):
|
||||
for _ in runner.tick(
|
||||
loop.tasks.values(),
|
||||
timeout=self.step_timeout,
|
||||
@@ -1654,11 +1823,16 @@ class Pregel(PregelProtocol):
|
||||
specs=self.channels,
|
||||
output_keys=output_keys,
|
||||
stream_keys=self.stream_channels_asis,
|
||||
interrupt_before=interrupt_before_,
|
||||
interrupt_after=interrupt_after_,
|
||||
manager=run_manager,
|
||||
debug=debug,
|
||||
) as loop:
|
||||
# create runner
|
||||
runner = PregelRunner(
|
||||
submit=loop.submit,
|
||||
put_writes=loop.put_writes,
|
||||
schedule_task=loop.accept_push,
|
||||
use_astream=do_stream is not None,
|
||||
node_finished=config[CONF].get(CONFIG_KEY_NODE_FINISHED),
|
||||
)
|
||||
@@ -1678,12 +1852,7 @@ class Pregel(PregelProtocol):
|
||||
# channel updates from step N are only visible in step N+1
|
||||
# channels are guaranteed to be immutable for the duration of the step,
|
||||
# with channel updates applied only at the transition between steps
|
||||
while loop.tick(
|
||||
input_keys=self.input_channels,
|
||||
interrupt_before=interrupt_before_,
|
||||
interrupt_after=interrupt_after_,
|
||||
manager=run_manager,
|
||||
):
|
||||
while loop.tick(input_keys=self.input_channels):
|
||||
async for _ in runner.atick(
|
||||
loop.tasks.values(),
|
||||
timeout=self.step_timeout,
|
||||
|
||||
@@ -25,6 +25,7 @@ from langgraph.channels.base import BaseChannel
|
||||
from langgraph.checkpoint.base import (
|
||||
BaseCheckpointSaver,
|
||||
Checkpoint,
|
||||
PendingWrite,
|
||||
V,
|
||||
copy_checkpoint,
|
||||
)
|
||||
@@ -35,17 +36,21 @@ from langgraph.constants import (
|
||||
CONFIG_KEY_CHECKPOINT_NS,
|
||||
CONFIG_KEY_CHECKPOINTER,
|
||||
CONFIG_KEY_READ,
|
||||
CONFIG_KEY_RESUME_VALUE,
|
||||
CONFIG_KEY_SEND,
|
||||
CONFIG_KEY_STORE,
|
||||
CONFIG_KEY_TASK_ID,
|
||||
EMPTY_SEQ,
|
||||
INTERRUPT,
|
||||
MISSING,
|
||||
NO_WRITES,
|
||||
NS_END,
|
||||
NS_SEP,
|
||||
NULL_TASK_ID,
|
||||
PULL,
|
||||
PUSH,
|
||||
RESERVED,
|
||||
RESUME,
|
||||
TAG_HIDDEN,
|
||||
TASKS,
|
||||
Send,
|
||||
@@ -68,7 +73,7 @@ class WritesProtocol(Protocol):
|
||||
Implemented by PregelTaskWrites and PregelExecutableTask."""
|
||||
|
||||
@property
|
||||
def path(self) -> tuple[Union[str, int], ...]: ...
|
||||
def path(self) -> tuple[Union[str, int, tuple], ...]: ...
|
||||
|
||||
@property
|
||||
def name(self) -> str: ...
|
||||
@@ -84,7 +89,7 @@ class PregelTaskWrites(NamedTuple):
|
||||
"""Simplest implementation of WritesProtocol, for usage with writes that
|
||||
don't originate from a runnable task, eg. graph input, update_state, etc."""
|
||||
|
||||
path: tuple[Union[str, int], ...]
|
||||
path: tuple[Union[str, int, tuple], ...]
|
||||
name: str
|
||||
writes: Sequence[tuple[str, Any]]
|
||||
triggers: Sequence[str]
|
||||
@@ -172,7 +177,7 @@ def local_write(
|
||||
"""Function injected under CONFIG_KEY_SEND in task config, to write to channels.
|
||||
Validates writes and forwards them to `commit` function."""
|
||||
for chan, value in writes:
|
||||
if chan == TASKS:
|
||||
if chan in (PUSH, TASKS):
|
||||
if not isinstance(value, Send):
|
||||
raise InvalidUpdateError(f"Expected Send, got {value}")
|
||||
if value.node not in process_keys:
|
||||
@@ -194,8 +199,13 @@ def apply_writes(
|
||||
"""Apply writes from a set of tasks (usually the tasks from a Pregel step)
|
||||
to the checkpoint and channels, and return managed values writes to be applied
|
||||
externally."""
|
||||
# sort tasks on path
|
||||
tasks = sorted(tasks, key=lambda t: t.path)
|
||||
# sort tasks on path, to ensure deterministic order for update application
|
||||
# any path parts after the 3rd are ignored for sorting
|
||||
# (we use them for eg. task ids which aren't good for sorting)
|
||||
tasks = sorted(tasks, key=lambda t: t.path[:3])
|
||||
# if no task has triggers this is applying writes from the null task only
|
||||
# so we don't do anything other than update the channels written to
|
||||
bump_step = any(t.triggers for t in tasks)
|
||||
|
||||
# update seen versions
|
||||
for task in tasks:
|
||||
@@ -227,7 +237,7 @@ def apply_writes(
|
||||
)
|
||||
|
||||
# clear pending sends
|
||||
if checkpoint["pending_sends"]:
|
||||
if checkpoint["pending_sends"] and bump_step:
|
||||
checkpoint["pending_sends"].clear()
|
||||
|
||||
# Group writes by channel
|
||||
@@ -235,9 +245,9 @@ def apply_writes(
|
||||
pending_writes_by_managed: dict[str, list[Any]] = defaultdict(list)
|
||||
for task in tasks:
|
||||
for chan, val in task.writes:
|
||||
if chan == NO_WRITES:
|
||||
if chan in (NO_WRITES, PUSH, RESUME, INTERRUPT):
|
||||
pass
|
||||
elif chan == TASKS:
|
||||
elif chan == TASKS: # TODO: remove branch in 1.0
|
||||
checkpoint["pending_sends"].append(val)
|
||||
elif chan in channels:
|
||||
pending_writes_by_channel[chan].append(val)
|
||||
@@ -262,13 +272,14 @@ def apply_writes(
|
||||
updated_channels.add(chan)
|
||||
|
||||
# Channels that weren't updated in this step are notified of a new step
|
||||
for chan in channels:
|
||||
if chan not in updated_channels:
|
||||
if channels[chan].update([]) and get_next_version is not None:
|
||||
checkpoint["channel_versions"][chan] = get_next_version(
|
||||
max_version,
|
||||
channels[chan],
|
||||
)
|
||||
if bump_step:
|
||||
for chan in channels:
|
||||
if chan not in updated_channels:
|
||||
if channels[chan].update([]) and get_next_version is not None:
|
||||
checkpoint["channel_versions"][chan] = get_next_version(
|
||||
max_version,
|
||||
channels[chan],
|
||||
)
|
||||
|
||||
# Return managed values writes to be applied externally
|
||||
return pending_writes_by_managed
|
||||
@@ -277,6 +288,7 @@ def apply_writes(
|
||||
@overload
|
||||
def prepare_next_tasks(
|
||||
checkpoint: Checkpoint,
|
||||
pending_writes: Sequence[PendingWrite],
|
||||
processes: Mapping[str, PregelNode],
|
||||
channels: Mapping[str, BaseChannel],
|
||||
managed: ManagedValueMapping,
|
||||
@@ -293,6 +305,7 @@ def prepare_next_tasks(
|
||||
@overload
|
||||
def prepare_next_tasks(
|
||||
checkpoint: Checkpoint,
|
||||
pending_writes: Sequence[PendingWrite],
|
||||
processes: Mapping[str, PregelNode],
|
||||
channels: Mapping[str, BaseChannel],
|
||||
managed: ManagedValueMapping,
|
||||
@@ -308,6 +321,7 @@ def prepare_next_tasks(
|
||||
|
||||
def prepare_next_tasks(
|
||||
checkpoint: Checkpoint,
|
||||
pending_writes: Sequence[PendingWrite],
|
||||
processes: Mapping[str, PregelNode],
|
||||
channels: Mapping[str, BaseChannel],
|
||||
managed: ManagedValueMapping,
|
||||
@@ -322,13 +336,14 @@ def prepare_next_tasks(
|
||||
"""Prepare the set of tasks that will make up the next Pregel step.
|
||||
This is the union of all PUSH tasks (Sends) and PULL tasks (nodes triggered
|
||||
by edges)."""
|
||||
tasks: dict[str, Union[PregelTask, PregelExecutableTask]] = {}
|
||||
# Consume pending packets
|
||||
for idx, _ in enumerate(checkpoint["pending_sends"]):
|
||||
tasks: list[Union[PregelTask, PregelExecutableTask]] = []
|
||||
# Consume pending_sends from previous step (legacy version of Send)
|
||||
for idx, _ in enumerate(checkpoint["pending_sends"]): # TODO: remove branch in 1.0
|
||||
if task := prepare_single_task(
|
||||
(PUSH, idx),
|
||||
None,
|
||||
checkpoint=checkpoint,
|
||||
pending_writes=pending_writes,
|
||||
processes=processes,
|
||||
channels=channels,
|
||||
managed=managed,
|
||||
@@ -339,7 +354,7 @@ def prepare_next_tasks(
|
||||
checkpointer=checkpointer,
|
||||
manager=manager,
|
||||
):
|
||||
tasks[task.id] = task
|
||||
tasks.append(task)
|
||||
# Check if any processes should be run in next step
|
||||
# If so, prepare the values to be passed to them
|
||||
for name in processes:
|
||||
@@ -347,6 +362,7 @@ def prepare_next_tasks(
|
||||
(PULL, name),
|
||||
None,
|
||||
checkpoint=checkpoint,
|
||||
pending_writes=pending_writes,
|
||||
processes=processes,
|
||||
channels=channels,
|
||||
managed=managed,
|
||||
@@ -357,15 +373,74 @@ def prepare_next_tasks(
|
||||
checkpointer=checkpointer,
|
||||
manager=manager,
|
||||
):
|
||||
tasks[task.id] = task
|
||||
return tasks
|
||||
tasks.append(task)
|
||||
# Consume pending Sends from this step (new version of Send)
|
||||
if any(c == PUSH for _, c, _ in pending_writes):
|
||||
# group writes by task id
|
||||
grouped_by_task = defaultdict(list)
|
||||
for tid, c, _ in pending_writes:
|
||||
grouped_by_task[tid].append(c)
|
||||
# prepare send tasks from grouped writes
|
||||
# 1. start from sends originating from existing tasks
|
||||
tidx = 0
|
||||
while tidx < len(tasks):
|
||||
task = tasks[tidx]
|
||||
if twrites := grouped_by_task.pop(task.id, None):
|
||||
for idx, c in enumerate(twrites):
|
||||
if c != PUSH:
|
||||
continue
|
||||
if next_task := prepare_single_task(
|
||||
(PUSH, task.path, idx, task.id),
|
||||
None,
|
||||
checkpoint=checkpoint,
|
||||
pending_writes=pending_writes,
|
||||
processes=processes,
|
||||
channels=channels,
|
||||
managed=managed,
|
||||
config=config,
|
||||
step=step,
|
||||
for_execution=for_execution,
|
||||
store=store,
|
||||
checkpointer=checkpointer,
|
||||
manager=manager,
|
||||
):
|
||||
tasks.append(next_task)
|
||||
tidx += 1
|
||||
# key tasks by id
|
||||
task_map = {t.id: t for t in tasks}
|
||||
# 2. create new tasks for remaining sends (eg. from update_state)
|
||||
for tid, writes in grouped_by_task.items():
|
||||
task = task_map.get(tid)
|
||||
for idx, c in enumerate(writes):
|
||||
if c != PUSH:
|
||||
continue
|
||||
if next_task := prepare_single_task(
|
||||
(PUSH, task.path if task else (), idx, tid),
|
||||
None,
|
||||
checkpoint=checkpoint,
|
||||
pending_writes=pending_writes,
|
||||
processes=processes,
|
||||
channels=channels,
|
||||
managed=managed,
|
||||
config=config,
|
||||
step=step,
|
||||
for_execution=for_execution,
|
||||
store=store,
|
||||
checkpointer=checkpointer,
|
||||
manager=manager,
|
||||
):
|
||||
task_map[next_task.id] = next_task
|
||||
else:
|
||||
task_map = {t.id: t for t in tasks}
|
||||
return task_map
|
||||
|
||||
|
||||
def prepare_single_task(
|
||||
task_path: tuple[str, Union[int, str]],
|
||||
task_path: tuple[Union[str, int, tuple], ...],
|
||||
task_id_checksum: Optional[str],
|
||||
*,
|
||||
checkpoint: Checkpoint,
|
||||
pending_writes: Sequence[PendingWrite],
|
||||
processes: Mapping[str, PregelNode],
|
||||
channels: Mapping[str, BaseChannel],
|
||||
managed: ManagedValueMapping,
|
||||
@@ -383,31 +458,74 @@ def prepare_single_task(
|
||||
parent_ns = configurable.get(CONFIG_KEY_CHECKPOINT_NS, "")
|
||||
|
||||
if task_path[0] == PUSH:
|
||||
idx = int(task_path[1])
|
||||
if idx >= len(checkpoint["pending_sends"]):
|
||||
return
|
||||
packet = checkpoint["pending_sends"][idx]
|
||||
if not isinstance(packet, Send):
|
||||
logger.warning(
|
||||
f"Ignoring invalid packet type {type(packet)} in pending sends"
|
||||
if len(task_path) == 2: # TODO: remove branch in 1.0
|
||||
# legacy SEND tasks, executed in superstep n+1
|
||||
# (PUSH, idx of pending send)
|
||||
idx = cast(int, task_path[1])
|
||||
if idx >= len(checkpoint["pending_sends"]):
|
||||
return
|
||||
packet = checkpoint["pending_sends"][idx]
|
||||
if not isinstance(packet, Send):
|
||||
logger.warning(
|
||||
f"Ignoring invalid packet type {type(packet)} in pending sends"
|
||||
)
|
||||
return
|
||||
if packet.node not in processes:
|
||||
logger.warning(
|
||||
f"Ignoring unknown node name {packet.node} in pending sends"
|
||||
)
|
||||
return
|
||||
# create task id
|
||||
triggers = [PUSH]
|
||||
checkpoint_ns = (
|
||||
f"{parent_ns}{NS_SEP}{packet.node}" if parent_ns else packet.node
|
||||
)
|
||||
task_id = _uuid5_str(
|
||||
checkpoint_id,
|
||||
checkpoint_ns,
|
||||
str(step),
|
||||
packet.node,
|
||||
PUSH,
|
||||
str(idx),
|
||||
)
|
||||
elif len(task_path) == 4:
|
||||
# new PUSH tasks, executed in superstep n
|
||||
# (PUSH, parent task path, idx of PUSH write, id of parent task)
|
||||
task_path_t = cast(tuple[str, tuple, int, str], task_path)
|
||||
writes_for_path = [w for w in pending_writes if w[0] == task_path_t[3]]
|
||||
if task_path_t[2] >= len(writes_for_path):
|
||||
logger.warning(
|
||||
f"Ignoring invalid write index {task_path[2]} in pending writes"
|
||||
)
|
||||
return
|
||||
packet = writes_for_path[task_path_t[2]][2]
|
||||
if not isinstance(packet, Send):
|
||||
logger.warning(
|
||||
f"Ignoring invalid packet type {type(packet)} in pending writes"
|
||||
)
|
||||
return
|
||||
if packet.node not in processes:
|
||||
logger.warning(
|
||||
f"Ignoring unknown node name {packet.node} in pending writes"
|
||||
)
|
||||
return
|
||||
# create task id
|
||||
triggers = [PUSH]
|
||||
checkpoint_ns = (
|
||||
f"{parent_ns}{NS_SEP}{packet.node}" if parent_ns else packet.node
|
||||
)
|
||||
task_id = _uuid5_str(
|
||||
checkpoint_id,
|
||||
checkpoint_ns,
|
||||
str(step),
|
||||
packet.node,
|
||||
PUSH,
|
||||
_tuple_str(task_path[1]),
|
||||
str(task_path[2]),
|
||||
)
|
||||
else:
|
||||
logger.warning(f"Ignoring invalid PUSH task path {task_path}")
|
||||
return
|
||||
if packet.node not in processes:
|
||||
logger.warning(f"Ignoring unknown node name {packet.node} in pending sends")
|
||||
return
|
||||
# create task id
|
||||
triggers = [PUSH]
|
||||
checkpoint_ns = (
|
||||
f"{parent_ns}{NS_SEP}{packet.node}" if parent_ns else packet.node
|
||||
)
|
||||
task_id = _uuid5_str(
|
||||
checkpoint_id,
|
||||
checkpoint_ns,
|
||||
str(step),
|
||||
packet.node,
|
||||
PUSH,
|
||||
str(idx),
|
||||
)
|
||||
task_checkpoint_ns = f"{checkpoint_ns}:{task_id}"
|
||||
metadata = {
|
||||
"langgraph_step": step,
|
||||
@@ -417,7 +535,7 @@ def prepare_single_task(
|
||||
"langgraph_checkpoint_ns": task_checkpoint_ns,
|
||||
}
|
||||
if task_id_checksum is not None:
|
||||
assert task_id == task_id_checksum
|
||||
assert task_id == task_id_checksum, f"{task_id} != {task_id_checksum}"
|
||||
if for_execution:
|
||||
proc = processes[packet.node]
|
||||
if node := proc.node:
|
||||
@@ -469,6 +587,14 @@ def prepare_single_task(
|
||||
},
|
||||
CONFIG_KEY_CHECKPOINT_ID: None,
|
||||
CONFIG_KEY_CHECKPOINT_NS: task_checkpoint_ns,
|
||||
CONFIG_KEY_RESUME_VALUE: next(
|
||||
(
|
||||
v
|
||||
for tid, c, v in pending_writes
|
||||
if tid in (NULL_TASK_ID, task_id) and c == RESUME
|
||||
),
|
||||
MISSING,
|
||||
),
|
||||
},
|
||||
),
|
||||
triggers,
|
||||
@@ -481,6 +607,7 @@ def prepare_single_task(
|
||||
else:
|
||||
return PregelTask(task_id, packet.node, task_path)
|
||||
elif task_path[0] == PULL:
|
||||
# (PULL, node name)
|
||||
name = cast(str, task_path[1])
|
||||
if name not in processes:
|
||||
return
|
||||
@@ -577,6 +704,15 @@ def prepare_single_task(
|
||||
},
|
||||
CONFIG_KEY_CHECKPOINT_ID: None,
|
||||
CONFIG_KEY_CHECKPOINT_NS: task_checkpoint_ns,
|
||||
CONFIG_KEY_RESUME_VALUE: next(
|
||||
(
|
||||
v
|
||||
for tid, c, v in pending_writes
|
||||
if tid in (NULL_TASK_ID, task_id)
|
||||
and c == RESUME
|
||||
),
|
||||
MISSING,
|
||||
),
|
||||
},
|
||||
),
|
||||
triggers,
|
||||
@@ -642,3 +778,12 @@ def _uuid5_str(namespace: bytes, *parts: str) -> str:
|
||||
sha.update(b"".join(p.encode() for p in parts))
|
||||
hex = sha.hexdigest()
|
||||
return f"{hex[:8]}-{hex[8:12]}-{hex[12:16]}-{hex[16:20]}-{hex[20:32]}"
|
||||
|
||||
|
||||
def _tuple_str(tup: Union[str, int, tuple]) -> str:
|
||||
"""Generate a string representation of a tuple."""
|
||||
return (
|
||||
f"({', '.join(_tuple_str(x) for x in tup)})"
|
||||
if isinstance(tup, (tuple, list))
|
||||
else str(tup)
|
||||
)
|
||||
@@ -191,6 +191,14 @@ def map_debug_checkpoint(
|
||||
"state": t.state,
|
||||
}
|
||||
if t.error
|
||||
else {
|
||||
"id": t.id,
|
||||
"name": t.name,
|
||||
"result": t.result,
|
||||
"interrupts": tuple(asdict(i) for i in t.interrupts),
|
||||
"state": t.state,
|
||||
}
|
||||
if t.result
|
||||
else {
|
||||
"id": t.id,
|
||||
"name": t.name,
|
||||
@@ -208,7 +216,7 @@ def print_step_tasks(step: int, next_tasks: list[PregelExecutableTask]) -> None:
|
||||
print(
|
||||
f"{get_colored_text(f'[{step}:tasks]', color='blue')} "
|
||||
+ get_bolded_text(
|
||||
f"Starting step {step} with {n_tasks} task{'s' if n_tasks != 1 else ''}:\n"
|
||||
f"Starting {n_tasks} task{'s' if n_tasks != 1 else ''} for step {step}:\n"
|
||||
)
|
||||
+ "\n".join(
|
||||
f"- {get_colored_text(task.name, 'green')} -> {pformat(task.input)}"
|
||||
|
||||
@@ -1,11 +1,31 @@
|
||||
from typing import Any, Iterator, Literal, Mapping, Optional, Sequence, TypeVar, Union
|
||||
from uuid import UUID
|
||||
|
||||
from langchain_core.runnables.utils import AddableDict
|
||||
|
||||
from langgraph.channels.base import BaseChannel, EmptyChannelError
|
||||
from langgraph.constants import EMPTY_SEQ, ERROR, INTERRUPT, TAG_HIDDEN
|
||||
from langgraph.constants import (
|
||||
EMPTY_SEQ,
|
||||
ERROR,
|
||||
FF_SEND_V2,
|
||||
INTERRUPT,
|
||||
NULL_TASK_ID,
|
||||
PUSH,
|
||||
RESUME,
|
||||
TAG_HIDDEN,
|
||||
TASKS,
|
||||
)
|
||||
from langgraph.pregel.log import logger
|
||||
from langgraph.types import PregelExecutableTask
|
||||
from langgraph.types import Command, PregelExecutableTask, Send
|
||||
|
||||
|
||||
def is_task_id(task_id: str) -> bool:
|
||||
"""Check if a string is a valid task id."""
|
||||
try:
|
||||
UUID(task_id)
|
||||
except ValueError:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def read_channel(
|
||||
@@ -44,6 +64,36 @@ def read_channels(
|
||||
return values
|
||||
|
||||
|
||||
def map_command(
|
||||
cmd: Command,
|
||||
) -> Iterator[tuple[str, str, Any]]:
|
||||
"""Map input chunk to a sequence of pending writes in the form (channel, value)."""
|
||||
if cmd.send:
|
||||
if isinstance(cmd.send, (tuple, list)):
|
||||
sends = cmd.send
|
||||
else:
|
||||
sends = [cmd.send]
|
||||
for send in sends:
|
||||
if not isinstance(send, Send):
|
||||
raise TypeError(
|
||||
f"In Command.send, expected Send, got {type(send).__name__}"
|
||||
)
|
||||
yield (NULL_TASK_ID, PUSH if FF_SEND_V2 else TASKS, send)
|
||||
if cmd.resume:
|
||||
if isinstance(cmd.resume, dict) and all(is_task_id(k) for k in cmd.resume):
|
||||
for tid, resume in cmd.resume.items():
|
||||
yield (tid, RESUME, resume)
|
||||
else:
|
||||
yield (NULL_TASK_ID, RESUME, cmd.resume)
|
||||
if cmd.update:
|
||||
if not isinstance(cmd.update, dict):
|
||||
raise TypeError(
|
||||
f"Expected cmd.update to be a dict mapping channel names to update values, got {type(cmd.update).__name__}"
|
||||
)
|
||||
for k, v in cmd.update.items():
|
||||
yield (NULL_TASK_ID, k, v)
|
||||
|
||||
|
||||
def map_input(
|
||||
input_channels: Union[str, Sequence[str]],
|
||||
chunk: Optional[Union[dict[str, Any], Any]],
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import asyncio
|
||||
import concurrent.futures
|
||||
from collections import deque
|
||||
from collections import defaultdict, deque
|
||||
from contextlib import AsyncExitStack, ExitStack
|
||||
from types import TracebackType
|
||||
from typing import (
|
||||
@@ -52,6 +52,9 @@ from langgraph.constants import (
|
||||
INPUT,
|
||||
INTERRUPT,
|
||||
NS_SEP,
|
||||
NULL_TASK_ID,
|
||||
PUSH,
|
||||
RESUME,
|
||||
SCHEDULED,
|
||||
TAG_HIDDEN,
|
||||
)
|
||||
@@ -74,6 +77,7 @@ from langgraph.pregel.algo import (
|
||||
apply_writes,
|
||||
increment,
|
||||
prepare_next_tasks,
|
||||
prepare_single_task,
|
||||
should_interrupt,
|
||||
)
|
||||
from langgraph.pregel.debug import (
|
||||
@@ -90,6 +94,7 @@ from langgraph.pregel.executor import (
|
||||
Submit,
|
||||
)
|
||||
from langgraph.pregel.io import (
|
||||
map_command,
|
||||
map_input,
|
||||
map_output_updates,
|
||||
map_output_values,
|
||||
@@ -100,7 +105,13 @@ from langgraph.pregel.manager import AsyncChannelsManager, ChannelsManager
|
||||
from langgraph.pregel.read import PregelNode
|
||||
from langgraph.pregel.utils import get_new_channel_versions
|
||||
from langgraph.store.base import BaseStore
|
||||
from langgraph.types import All, LoopProtocol, PregelExecutableTask, StreamProtocol
|
||||
from langgraph.types import (
|
||||
All,
|
||||
Command,
|
||||
LoopProtocol,
|
||||
PregelExecutableTask,
|
||||
StreamProtocol,
|
||||
)
|
||||
from langgraph.utils.config import patch_configurable
|
||||
|
||||
V = TypeVar("V")
|
||||
@@ -130,6 +141,9 @@ class PregelLoop(LoopProtocol):
|
||||
stream_keys: Union[str, Sequence[str]]
|
||||
skip_done_tasks: bool
|
||||
is_nested: bool
|
||||
manager: Union[None, AsyncParentRunManager, ParentRunManager]
|
||||
interrupt_after: Union[All, Sequence[str]]
|
||||
interrupt_before: Union[All, Sequence[str]]
|
||||
|
||||
checkpointer_get_next_version: GetNextVersion
|
||||
checkpointer_put_writes: Optional[
|
||||
@@ -162,6 +176,7 @@ class PregelLoop(LoopProtocol):
|
||||
"pending", "done", "interrupt_before", "interrupt_after", "out_of_steps"
|
||||
]
|
||||
tasks: dict[str, PregelExecutableTask]
|
||||
to_interrupt: list[PregelExecutableTask]
|
||||
output: Union[None, dict[str, Any], Any] = None
|
||||
|
||||
# public
|
||||
@@ -178,6 +193,9 @@ class PregelLoop(LoopProtocol):
|
||||
specs: Mapping[str, Union[BaseChannel, ManagedValueSpec]],
|
||||
output_keys: Union[str, Sequence[str]],
|
||||
stream_keys: Union[str, Sequence[str]],
|
||||
interrupt_after: Union[All, Sequence[str]] = EMPTY_SEQ,
|
||||
interrupt_before: Union[All, Sequence[str]] = EMPTY_SEQ,
|
||||
manager: Union[None, AsyncParentRunManager, ParentRunManager] = None,
|
||||
check_subgraphs: bool = True,
|
||||
debug: bool = False,
|
||||
) -> None:
|
||||
@@ -194,6 +212,9 @@ class PregelLoop(LoopProtocol):
|
||||
self.specs = specs
|
||||
self.output_keys = output_keys
|
||||
self.stream_keys = stream_keys
|
||||
self.interrupt_after = interrupt_after
|
||||
self.interrupt_before = interrupt_before
|
||||
self.manager = manager
|
||||
self.is_nested = CONFIG_KEY_TASK_ID in self.config.get(CONF, {})
|
||||
self.skip_done_tasks = (
|
||||
CONFIG_KEY_CHECKPOINT_ID not in config[CONF]
|
||||
@@ -261,15 +282,60 @@ class PregelLoop(LoopProtocol):
|
||||
task_id,
|
||||
)
|
||||
# output writes
|
||||
self._output_writes(task_id, writes)
|
||||
if hasattr(self, "tasks"):
|
||||
self._output_writes(task_id, writes)
|
||||
|
||||
def accept_push(
|
||||
self, task: PregelExecutableTask, write_idx: int
|
||||
) -> Optional[PregelExecutableTask]:
|
||||
"""Accept a PUSH from a task, potentially returning a new task to start."""
|
||||
# don't start if an earlier PUSH has already triggered an interrupt
|
||||
if self.to_interrupt:
|
||||
return
|
||||
# don't start if we should interrupt *after* the original task
|
||||
if should_interrupt(self.checkpoint, self.interrupt_after, [task]):
|
||||
self.to_interrupt.append(task)
|
||||
return
|
||||
if pushed := cast(
|
||||
Optional[PregelExecutableTask],
|
||||
prepare_single_task(
|
||||
(PUSH, task.path, write_idx, task.id),
|
||||
None,
|
||||
checkpoint=self.checkpoint,
|
||||
pending_writes=[(task.id, *w) for w in task.writes],
|
||||
processes=self.nodes,
|
||||
channels=self.channels,
|
||||
managed=self.managed,
|
||||
config=self.config,
|
||||
step=self.step,
|
||||
for_execution=True,
|
||||
store=self.store,
|
||||
checkpointer=self.checkpointer,
|
||||
manager=self.manager,
|
||||
),
|
||||
):
|
||||
# don't start if we should interrupt *before* the new task
|
||||
if should_interrupt(self.checkpoint, self.interrupt_before, [pushed]):
|
||||
self.to_interrupt.append(pushed)
|
||||
return
|
||||
# produce debug output
|
||||
self._emit("debug", map_debug_tasks, self.step, [pushed])
|
||||
# debug flag
|
||||
if self.debug:
|
||||
print_step_tasks(self.step, [pushed])
|
||||
# save the new task
|
||||
self.tasks[pushed.id] = pushed
|
||||
# match any pending writes to the new task
|
||||
if self.skip_done_tasks:
|
||||
self._match_writes({pushed.id: pushed})
|
||||
# return the new task, to be started, if not run before
|
||||
if not pushed.writes:
|
||||
return pushed
|
||||
|
||||
def tick(
|
||||
self,
|
||||
*,
|
||||
input_keys: Union[str, Sequence[str]],
|
||||
interrupt_after: Union[All, Sequence[str]] = EMPTY_SEQ,
|
||||
interrupt_before: Union[All, Sequence[str]] = EMPTY_SEQ,
|
||||
manager: Union[None, AsyncParentRunManager, ParentRunManager] = None,
|
||||
) -> bool:
|
||||
"""Execute a single iteration of the Pregel loop.
|
||||
Returns True if more iterations are needed."""
|
||||
@@ -278,6 +344,10 @@ class PregelLoop(LoopProtocol):
|
||||
|
||||
if self.input not in (INPUT_DONE, INPUT_RESUMING):
|
||||
self._first(input_keys=input_keys)
|
||||
elif self.to_interrupt:
|
||||
# if we need to interrupt, do so
|
||||
self.status = "interrupt_before"
|
||||
raise GraphInterrupt()
|
||||
elif all(task.writes for task in self.tasks.values()):
|
||||
writes = [w for t in self.tasks.values() for w in t.writes]
|
||||
# debug flag
|
||||
@@ -322,7 +392,9 @@ class PregelLoop(LoopProtocol):
|
||||
}
|
||||
)
|
||||
# after execution, check if we should interrupt
|
||||
if should_interrupt(self.checkpoint, interrupt_after, self.tasks.values()):
|
||||
if should_interrupt(
|
||||
self.checkpoint, self.interrupt_after, self.tasks.values()
|
||||
):
|
||||
self.status = "interrupt_after"
|
||||
raise GraphInterrupt()
|
||||
else:
|
||||
@@ -333,19 +405,33 @@ class PregelLoop(LoopProtocol):
|
||||
self.status = "out_of_steps"
|
||||
return False
|
||||
|
||||
# apply NULL writes
|
||||
if null_writes := [
|
||||
w[1:] for w in self.checkpoint_pending_writes if w[0] == NULL_TASK_ID
|
||||
]:
|
||||
mv_writes = apply_writes(
|
||||
self.checkpoint,
|
||||
self.channels,
|
||||
[PregelTaskWrites((), INPUT, null_writes, [])],
|
||||
self.checkpointer_get_next_version,
|
||||
)
|
||||
for key, values in mv_writes.items():
|
||||
self._update_mv(key, values)
|
||||
# prepare next tasks
|
||||
self.tasks = prepare_next_tasks(
|
||||
self.checkpoint,
|
||||
self.checkpoint_pending_writes,
|
||||
self.nodes,
|
||||
self.channels,
|
||||
self.managed,
|
||||
self.config,
|
||||
self.step,
|
||||
for_execution=True,
|
||||
manager=manager,
|
||||
manager=self.manager,
|
||||
store=self.store,
|
||||
checkpointer=self.checkpointer,
|
||||
)
|
||||
self.to_interrupt = []
|
||||
|
||||
# produce debug output
|
||||
if self._checkpointer_put_after_previous is not None:
|
||||
@@ -387,15 +473,12 @@ class PregelLoop(LoopProtocol):
|
||||
|
||||
# if all tasks have finished, re-tick
|
||||
if all(task.writes for task in self.tasks.values()):
|
||||
return self.tick(
|
||||
input_keys=input_keys,
|
||||
interrupt_after=interrupt_after,
|
||||
interrupt_before=interrupt_before,
|
||||
manager=manager,
|
||||
)
|
||||
return self.tick(input_keys=input_keys)
|
||||
|
||||
# before execution, check if we should interrupt
|
||||
if should_interrupt(self.checkpoint, interrupt_before, self.tasks.values()):
|
||||
if should_interrupt(
|
||||
self.checkpoint, self.interrupt_before, self.tasks.values()
|
||||
):
|
||||
self.status = "interrupt_before"
|
||||
raise GraphInterrupt()
|
||||
|
||||
@@ -417,7 +500,7 @@ class PregelLoop(LoopProtocol):
|
||||
|
||||
def _match_writes(self, tasks: Mapping[str, PregelExecutableTask]) -> None:
|
||||
for tid, k, v in self.checkpoint_pending_writes:
|
||||
if k in (ERROR, INTERRUPT):
|
||||
if k in (ERROR, INTERRUPT, RESUME):
|
||||
continue
|
||||
if task := tasks.get(tid):
|
||||
if k == SCHEDULED:
|
||||
@@ -449,8 +532,20 @@ class PregelLoop(LoopProtocol):
|
||||
self._emit(
|
||||
"values", map_output_values, self.output_keys, True, self.channels
|
||||
)
|
||||
# map command to writes
|
||||
elif isinstance(self.input, Command):
|
||||
writes: defaultdict[str, list[tuple[str, Any]]] = defaultdict(list)
|
||||
# group writes by task ID
|
||||
for tid, c, v in map_command(self.input):
|
||||
writes[tid].append((c, v))
|
||||
if not writes:
|
||||
raise EmptyInputError("Received empty Command input")
|
||||
# save writes
|
||||
for tid, ws in writes.items():
|
||||
self.put_writes(tid, ws)
|
||||
# map inputs to channel updates
|
||||
elif input_writes := deque(map_input(input_keys, self.input)):
|
||||
# TODO shouldn't these writes be passed to put_writes too?
|
||||
# check if we should delegate (used by subgraphs in distributed mode)
|
||||
if self.config[CONF].get(CONFIG_KEY_DELEGATE):
|
||||
raise GraphDelegate(
|
||||
@@ -464,6 +559,7 @@ class PregelLoop(LoopProtocol):
|
||||
# discard any unfinished tasks from previous checkpoint
|
||||
discard_tasks = prepare_next_tasks(
|
||||
self.checkpoint,
|
||||
self.checkpoint_pending_writes,
|
||||
self.nodes,
|
||||
self.channels,
|
||||
self.managed,
|
||||
@@ -577,11 +673,33 @@ class PregelLoop(LoopProtocol):
|
||||
# save final output
|
||||
self.output = read_channels(self.channels, self.output_keys)
|
||||
if suppress:
|
||||
# suppress interrupt
|
||||
# emit one last "values" event, with pending writes applied
|
||||
if (
|
||||
hasattr(self, "tasks")
|
||||
and self.checkpoint_pending_writes
|
||||
and any(task.writes for task in self.tasks.values())
|
||||
):
|
||||
mv_writes = apply_writes(
|
||||
self.checkpoint,
|
||||
self.channels,
|
||||
self.tasks.values(),
|
||||
self.checkpointer_get_next_version,
|
||||
)
|
||||
for key, values in mv_writes.items():
|
||||
self._update_mv(key, values)
|
||||
self._emit(
|
||||
"values",
|
||||
map_output_values,
|
||||
self.output_keys,
|
||||
[w for t in self.tasks.values() for w in t.writes],
|
||||
self.channels,
|
||||
)
|
||||
# emit INTERRUPT event
|
||||
self._emit(
|
||||
"updates",
|
||||
lambda: iter([{INTERRUPT: cast(GraphInterrupt, exc_value).args[0]}]),
|
||||
)
|
||||
# suppress interrupt
|
||||
return True
|
||||
|
||||
def _emit(
|
||||
@@ -635,6 +753,9 @@ class SyncPregelLoop(PregelLoop, ContextManager):
|
||||
checkpointer: Optional[BaseCheckpointSaver],
|
||||
nodes: Mapping[str, PregelNode],
|
||||
specs: Mapping[str, Union[BaseChannel, ManagedValueSpec]],
|
||||
manager: Union[None, AsyncParentRunManager, ParentRunManager] = None,
|
||||
interrupt_after: Union[All, Sequence[str]] = EMPTY_SEQ,
|
||||
interrupt_before: Union[All, Sequence[str]] = EMPTY_SEQ,
|
||||
output_keys: Union[str, Sequence[str]] = EMPTY_SEQ,
|
||||
stream_keys: Union[str, Sequence[str]] = EMPTY_SEQ,
|
||||
check_subgraphs: bool = True,
|
||||
@@ -650,7 +771,10 @@ class SyncPregelLoop(PregelLoop, ContextManager):
|
||||
specs=specs,
|
||||
output_keys=output_keys,
|
||||
stream_keys=stream_keys,
|
||||
interrupt_after=interrupt_after,
|
||||
interrupt_before=interrupt_before,
|
||||
check_subgraphs=check_subgraphs,
|
||||
manager=manager,
|
||||
debug=debug,
|
||||
)
|
||||
self.stack = ExitStack()
|
||||
@@ -761,6 +885,9 @@ class AsyncPregelLoop(PregelLoop, AsyncContextManager):
|
||||
checkpointer: Optional[BaseCheckpointSaver],
|
||||
nodes: Mapping[str, PregelNode],
|
||||
specs: Mapping[str, Union[BaseChannel, ManagedValueSpec]],
|
||||
interrupt_after: Union[All, Sequence[str]] = EMPTY_SEQ,
|
||||
interrupt_before: Union[All, Sequence[str]] = EMPTY_SEQ,
|
||||
manager: Union[None, AsyncParentRunManager, ParentRunManager] = None,
|
||||
output_keys: Union[str, Sequence[str]] = EMPTY_SEQ,
|
||||
stream_keys: Union[str, Sequence[str]] = EMPTY_SEQ,
|
||||
check_subgraphs: bool = True,
|
||||
@@ -776,7 +903,10 @@ class AsyncPregelLoop(PregelLoop, AsyncContextManager):
|
||||
specs=specs,
|
||||
output_keys=output_keys,
|
||||
stream_keys=stream_keys,
|
||||
interrupt_after=interrupt_after,
|
||||
interrupt_before=interrupt_before,
|
||||
check_subgraphs=check_subgraphs,
|
||||
manager=manager,
|
||||
debug=debug,
|
||||
)
|
||||
self.stack = AsyncExitStack()
|
||||
|
||||
@@ -2,9 +2,15 @@ import asyncio
|
||||
import logging
|
||||
import random
|
||||
import time
|
||||
from typing import Optional, Sequence
|
||||
from functools import partial
|
||||
from typing import Any, Callable, Optional, Sequence
|
||||
|
||||
from langgraph.constants import CONF, CONFIG_KEY_CHECKPOINT_NS, CONFIG_KEY_RESUMING
|
||||
from langgraph.constants import (
|
||||
CONF,
|
||||
CONFIG_KEY_CHECKPOINT_NS,
|
||||
CONFIG_KEY_RESUMING,
|
||||
CONFIG_KEY_SEND,
|
||||
)
|
||||
from langgraph.errors import _SEEN_CHECKPOINT_NS, GraphInterrupt
|
||||
from langgraph.types import PregelExecutableTask, RetryPolicy
|
||||
from langgraph.utils.config import patch_configurable
|
||||
@@ -15,12 +21,17 @@ logger = logging.getLogger(__name__)
|
||||
def run_with_retry(
|
||||
task: PregelExecutableTask,
|
||||
retry_policy: Optional[RetryPolicy],
|
||||
writer: Optional[
|
||||
Callable[[PregelExecutableTask, Sequence[tuple[str, Any]]], None]
|
||||
] = None,
|
||||
) -> None:
|
||||
"""Run a task with retries."""
|
||||
retry_policy = task.retry_policy or retry_policy
|
||||
interval = retry_policy.initial_interval if retry_policy else 0
|
||||
attempts = 0
|
||||
config = task.config
|
||||
if writer is not None:
|
||||
config = patch_configurable(config, {CONFIG_KEY_SEND: partial(writer, task)})
|
||||
while True:
|
||||
try:
|
||||
# clear any writes from previous attempts
|
||||
@@ -84,12 +95,17 @@ async def arun_with_retry(
|
||||
task: PregelExecutableTask,
|
||||
retry_policy: Optional[RetryPolicy],
|
||||
stream: bool = False,
|
||||
writer: Optional[
|
||||
Callable[[PregelExecutableTask, Sequence[tuple[str, Any]]], None]
|
||||
] = None,
|
||||
) -> None:
|
||||
"""Run a task asynchronously with retries."""
|
||||
retry_policy = task.retry_policy or retry_policy
|
||||
interval = retry_policy.initial_interval if retry_policy else 0
|
||||
attempts = 0
|
||||
config = task.config
|
||||
if writer is not None:
|
||||
config = patch_configurable(config, {CONFIG_KEY_SEND: partial(writer, task)})
|
||||
while True:
|
||||
try:
|
||||
# clear any writes from previous attempts
|
||||
|
||||
@@ -14,7 +14,15 @@ from typing import (
|
||||
cast,
|
||||
)
|
||||
|
||||
from langgraph.constants import ERROR, INTERRUPT, NO_WRITES, TAG_HIDDEN
|
||||
from langgraph.constants import (
|
||||
CONF,
|
||||
CONFIG_KEY_SEND,
|
||||
ERROR,
|
||||
INTERRUPT,
|
||||
NO_WRITES,
|
||||
PUSH,
|
||||
TAG_HIDDEN,
|
||||
)
|
||||
from langgraph.errors import GraphDelegate, GraphInterrupt
|
||||
from langgraph.pregel.executor import Submit
|
||||
from langgraph.pregel.retry import arun_with_retry, run_with_retry
|
||||
@@ -31,6 +39,9 @@ class PregelRunner:
|
||||
*,
|
||||
submit: Submit,
|
||||
put_writes: Callable[[str, Sequence[tuple[str, Any]]], None],
|
||||
schedule_task: Callable[
|
||||
[PregelExecutableTask, int], Optional[PregelExecutableTask]
|
||||
],
|
||||
use_astream: bool = False,
|
||||
node_finished: Optional[Callable[[str], None]] = None,
|
||||
) -> None:
|
||||
@@ -38,6 +49,7 @@ class PregelRunner:
|
||||
self.put_writes = put_writes
|
||||
self.use_astream = use_astream
|
||||
self.node_finished = node_finished
|
||||
self.schedule_task = schedule_task
|
||||
|
||||
def tick(
|
||||
self,
|
||||
@@ -48,27 +60,58 @@ class PregelRunner:
|
||||
retry_policy: Optional[RetryPolicy] = None,
|
||||
get_waiter: Optional[Callable[[], concurrent.futures.Future[None]]] = None,
|
||||
) -> Iterator[None]:
|
||||
def writer(
|
||||
task: PregelExecutableTask, writes: Sequence[tuple[str, Any]]
|
||||
) -> None:
|
||||
prev_length = len(task.writes)
|
||||
# delegate to the underlying writer
|
||||
task.config[CONF][CONFIG_KEY_SEND](writes)
|
||||
for idx, w in enumerate(task.writes):
|
||||
# find the index for the newly inserted writes
|
||||
if idx < prev_length:
|
||||
continue
|
||||
assert writes[idx - prev_length] is w
|
||||
# bail if not a PUSH write
|
||||
if w[0] != PUSH:
|
||||
continue
|
||||
# schedule the next task, if the callback returns one
|
||||
if next_task := self.schedule_task(task, idx):
|
||||
# if the parent task was retried,
|
||||
# the next task might already be running
|
||||
if any(
|
||||
t == next_task.id for t in futures.values() if t is not None
|
||||
):
|
||||
continue
|
||||
# schedule the next task
|
||||
futures[
|
||||
self.submit(
|
||||
run_with_retry,
|
||||
next_task,
|
||||
retry_policy,
|
||||
writer=writer,
|
||||
__reraise_on_exit__=reraise,
|
||||
)
|
||||
] = next_task
|
||||
|
||||
tasks = tuple(tasks)
|
||||
futures: dict[concurrent.futures.Future, Optional[PregelExecutableTask]] = {}
|
||||
# give control back to the caller
|
||||
yield
|
||||
# fast path if single task with no timeout and no waiter
|
||||
if len(tasks) == 1 and timeout is None and get_waiter is None:
|
||||
t = tasks[0]
|
||||
try:
|
||||
run_with_retry(t, retry_policy)
|
||||
run_with_retry(t, retry_policy, writer=writer)
|
||||
self.commit(t, None)
|
||||
except Exception as exc:
|
||||
self.commit(t, exc)
|
||||
if reraise:
|
||||
raise
|
||||
return
|
||||
if not futures: # maybe `t` schuduled another task
|
||||
return
|
||||
# add waiter task if requested
|
||||
if get_waiter is not None:
|
||||
futures: dict[concurrent.futures.Future, Optional[PregelExecutableTask]] = {
|
||||
get_waiter(): None
|
||||
}
|
||||
else:
|
||||
futures = {}
|
||||
futures[get_waiter()] = None
|
||||
# execute tasks, and wait for one to fail or all to finish.
|
||||
# each task is independent from all other concurrent tasks
|
||||
# yield updates/debug output as each task finishes
|
||||
@@ -79,10 +122,11 @@ class PregelRunner:
|
||||
run_with_retry,
|
||||
t,
|
||||
retry_policy,
|
||||
writer=writer,
|
||||
__reraise_on_exit__=reraise,
|
||||
)
|
||||
] = t
|
||||
all_futures = futures.copy()
|
||||
done_futures: set[concurrent.futures.Future] = set()
|
||||
end_time = timeout + time.monotonic() if timeout else None
|
||||
while len(futures) > (1 if get_waiter is not None else 0):
|
||||
done, inflight = concurrent.futures.wait(
|
||||
@@ -99,6 +143,8 @@ class PregelRunner:
|
||||
if inflight and get_waiter is not None:
|
||||
futures[get_waiter()] = None
|
||||
else:
|
||||
# store for panic check
|
||||
done_futures.add(fut)
|
||||
# task finished, commit writes
|
||||
self.commit(task, _exception(fut))
|
||||
else:
|
||||
@@ -110,7 +156,10 @@ class PregelRunner:
|
||||
# give control back to the caller
|
||||
yield
|
||||
# panic on failure or timeout
|
||||
_panic_or_proceed(all_futures, panic=reraise)
|
||||
_panic_or_proceed(
|
||||
done_futures.union(f for f, t in futures.items() if t is not None),
|
||||
panic=reraise,
|
||||
)
|
||||
|
||||
async def atick(
|
||||
self,
|
||||
@@ -121,28 +170,67 @@ class PregelRunner:
|
||||
retry_policy: Optional[RetryPolicy] = None,
|
||||
get_waiter: Optional[Callable[[], asyncio.Future[None]]] = None,
|
||||
) -> AsyncIterator[None]:
|
||||
def writer(
|
||||
task: PregelExecutableTask, writes: Sequence[tuple[str, Any]]
|
||||
) -> None:
|
||||
prev_length = len(task.writes)
|
||||
# delegate to the underlying writer
|
||||
task.config[CONF][CONFIG_KEY_SEND](writes)
|
||||
for idx, w in enumerate(task.writes):
|
||||
# find the index for the newly inserted writes
|
||||
if idx < prev_length:
|
||||
continue
|
||||
assert writes[idx - prev_length] is w
|
||||
# bail if not a PUSH write
|
||||
if w[0] != PUSH:
|
||||
continue
|
||||
# schedule the next task, if the callback returns one
|
||||
if next_task := self.schedule_task(task, idx):
|
||||
# if the parent task was retried,
|
||||
# the next task might already be running
|
||||
if any(
|
||||
t == next_task.id for t in futures.values() if t is not None
|
||||
):
|
||||
continue
|
||||
# schedule the next task
|
||||
futures[
|
||||
cast(
|
||||
asyncio.Future,
|
||||
self.submit(
|
||||
arun_with_retry,
|
||||
next_task,
|
||||
retry_policy,
|
||||
stream=self.use_astream,
|
||||
writer=writer,
|
||||
__name__=t.name,
|
||||
__cancel_on_exit__=True,
|
||||
__reraise_on_exit__=reraise,
|
||||
),
|
||||
)
|
||||
] = next_task
|
||||
|
||||
loop = asyncio.get_event_loop()
|
||||
tasks = tuple(tasks)
|
||||
futures: dict[asyncio.Future, Optional[PregelExecutableTask]] = {}
|
||||
# give control back to the caller
|
||||
yield
|
||||
# fast path if single task with no waiter and no timeout
|
||||
if len(tasks) == 1 and get_waiter is None and timeout is None:
|
||||
t = tasks[0]
|
||||
try:
|
||||
await arun_with_retry(t, retry_policy, stream=self.use_astream)
|
||||
await arun_with_retry(
|
||||
t, retry_policy, stream=self.use_astream, writer=writer
|
||||
)
|
||||
self.commit(t, None)
|
||||
except Exception as exc:
|
||||
self.commit(t, exc)
|
||||
if reraise:
|
||||
raise
|
||||
return
|
||||
if not futures: # maybe `t` schuduled another task
|
||||
return
|
||||
# add waiter task if requested
|
||||
if get_waiter is not None:
|
||||
futures: dict[asyncio.Future, Optional[PregelExecutableTask]] = {
|
||||
get_waiter(): None
|
||||
}
|
||||
else:
|
||||
futures = {}
|
||||
futures[get_waiter()] = None
|
||||
# execute tasks, and wait for one to fail or all to finish.
|
||||
# each task is independent from all other concurrent tasks
|
||||
# yield updates/debug output as each task finishes
|
||||
@@ -156,13 +244,14 @@ class PregelRunner:
|
||||
t,
|
||||
retry_policy,
|
||||
stream=self.use_astream,
|
||||
writer=writer,
|
||||
__name__=t.name,
|
||||
__cancel_on_exit__=True,
|
||||
__reraise_on_exit__=reraise,
|
||||
),
|
||||
)
|
||||
] = t
|
||||
all_futures = futures.copy()
|
||||
done_futures: set[asyncio.Future] = set()
|
||||
end_time = timeout + loop.time() if timeout else None
|
||||
while len(futures) > (1 if get_waiter is not None else 0):
|
||||
done, inflight = await asyncio.wait(
|
||||
@@ -179,6 +268,8 @@ class PregelRunner:
|
||||
if inflight and get_waiter is not None:
|
||||
futures[get_waiter()] = None
|
||||
else:
|
||||
# store for panic check
|
||||
done_futures.add(fut)
|
||||
# task finished, commit writes
|
||||
self.commit(task, _exception(fut))
|
||||
else:
|
||||
@@ -194,7 +285,9 @@ class PregelRunner:
|
||||
fut.cancel()
|
||||
# panic on failure or timeout
|
||||
_panic_or_proceed(
|
||||
all_futures, timeout_exc_cls=asyncio.TimeoutError, panic=reraise
|
||||
done_futures.union(f for f, t in futures.items() if t is not None),
|
||||
timeout_exc_cls=asyncio.TimeoutError,
|
||||
panic=reraise,
|
||||
)
|
||||
|
||||
def commit(
|
||||
@@ -250,10 +343,7 @@ def _exception(
|
||||
|
||||
|
||||
def _panic_or_proceed(
|
||||
futs: Union[
|
||||
dict[concurrent.futures.Future, Optional[PregelExecutableTask]],
|
||||
dict[asyncio.Future, Optional[PregelExecutableTask]],
|
||||
],
|
||||
futs: Union[set[concurrent.futures.Future], set[asyncio.Future]],
|
||||
*,
|
||||
timeout_exc_cls: Type[Exception] = TimeoutError,
|
||||
panic: bool = True,
|
||||
@@ -261,10 +351,8 @@ def _panic_or_proceed(
|
||||
"""Cancel remaining tasks if any failed, re-raise exception if panic is True."""
|
||||
done: set[Union[concurrent.futures.Future[Any], asyncio.Future[Any]]] = set()
|
||||
inflight: set[Union[concurrent.futures.Future[Any], asyncio.Future[Any]]] = set()
|
||||
for fut, val in futs.items():
|
||||
if val is None:
|
||||
continue
|
||||
elif fut.done():
|
||||
for fut in futs:
|
||||
if fut.done():
|
||||
done.add(fut)
|
||||
else:
|
||||
inflight.add(fut)
|
||||
|
||||
@@ -48,7 +48,7 @@ def find_subgraph_pregel(candidate: Runnable) -> Optional[Runnable]:
|
||||
nl.__self__ if hasattr(nl, "__self__") else nl
|
||||
for nl in get_function_nonlocals(c.func)
|
||||
)
|
||||
if c.afunc is not None:
|
||||
elif c.afunc is not None:
|
||||
candidates.extend(
|
||||
nl.__self__ if hasattr(nl, "__self__") else nl
|
||||
for nl in get_function_nonlocals(c.afunc)
|
||||
|
||||
@@ -14,7 +14,7 @@ from typing import (
|
||||
from langchain_core.runnables import Runnable, RunnableConfig
|
||||
from langchain_core.runnables.utils import ConfigurableFieldSpec
|
||||
|
||||
from langgraph.constants import CONF, CONFIG_KEY_SEND, TASKS, Send
|
||||
from langgraph.constants import CONF, CONFIG_KEY_SEND, FF_SEND_V2, PUSH, TASKS, Send
|
||||
from langgraph.errors import InvalidUpdateError
|
||||
from langgraph.utils.runnable import RunnableCallable
|
||||
|
||||
@@ -112,14 +112,18 @@ class ChannelWrite(RunnableCallable):
|
||||
# validate
|
||||
for w in writes:
|
||||
if isinstance(w, ChannelWriteEntry):
|
||||
if w.channel == TASKS:
|
||||
if w.channel in (TASKS, PUSH):
|
||||
raise InvalidUpdateError(
|
||||
"Cannot write to the reserved channel TASKS"
|
||||
)
|
||||
if w.value is PASSTHROUGH:
|
||||
raise InvalidUpdateError("PASSTHROUGH value must be replaced")
|
||||
# split packets and entries
|
||||
sends = [(TASKS, packet) for packet in writes if isinstance(packet, Send)]
|
||||
sends = [
|
||||
(PUSH if FF_SEND_V2 else TASKS, packet)
|
||||
for packet in writes
|
||||
if isinstance(packet, Send)
|
||||
]
|
||||
entries = [write for write in writes if isinstance(write, ChannelWriteEntry)]
|
||||
# process entries into values
|
||||
values = [
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
import dataclasses
|
||||
import sys
|
||||
from collections import deque
|
||||
from dataclasses import dataclass
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
Callable,
|
||||
Generic,
|
||||
Hashable,
|
||||
Literal,
|
||||
NamedTuple,
|
||||
Optional,
|
||||
Sequence,
|
||||
Type,
|
||||
TypeVar,
|
||||
Union,
|
||||
cast,
|
||||
)
|
||||
@@ -44,6 +48,11 @@ StreamWriter = Callable[[Any], None]
|
||||
Always injected into nodes if requested as a keyword argument, but it's a no-op
|
||||
when not using stream_mode="custom"."""
|
||||
|
||||
if sys.version_info >= (3, 10):
|
||||
_DC_KWARGS = {"kw_only": True, "slots": True, "frozen": True}
|
||||
else:
|
||||
_DC_KWARGS = {"frozen": True}
|
||||
|
||||
|
||||
def default_retry_on(exc: Exception) -> bool:
|
||||
import httpx
|
||||
@@ -101,16 +110,18 @@ class CachePolicy(NamedTuple):
|
||||
pass
|
||||
|
||||
|
||||
@dataclass
|
||||
@dataclasses.dataclass(**_DC_KWARGS)
|
||||
class Interrupt:
|
||||
value: Any
|
||||
resumable: bool = False
|
||||
ns: Optional[Sequence[str]] = None
|
||||
when: Literal["during"] = "during"
|
||||
|
||||
|
||||
class PregelTask(NamedTuple):
|
||||
id: str
|
||||
name: str
|
||||
path: tuple[Union[str, int], ...]
|
||||
path: tuple[Union[str, int, tuple], ...]
|
||||
error: Optional[Exception] = None
|
||||
interrupts: tuple[Interrupt, ...] = ()
|
||||
state: Union[None, RunnableConfig, "StateSnapshot"] = None
|
||||
@@ -127,7 +138,7 @@ class PregelExecutableTask(NamedTuple):
|
||||
retry_policy: Optional[RetryPolicy]
|
||||
cache_policy: Optional[CachePolicy]
|
||||
id: str
|
||||
path: tuple[Union[str, int], ...]
|
||||
path: tuple[Union[str, int, tuple], ...]
|
||||
scheduled: bool = False
|
||||
|
||||
|
||||
@@ -221,51 +232,26 @@ class Send:
|
||||
)
|
||||
|
||||
|
||||
class Command:
|
||||
N = TypeVar("N", bound=Hashable)
|
||||
|
||||
|
||||
@dataclasses.dataclass(**_DC_KWARGS)
|
||||
class Command(Generic[N]):
|
||||
"""One or more commands to update the graph's state and send messages to nodes."""
|
||||
|
||||
__slots__ = ("update", "send")
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
update: Optional[dict[str, Any]] = None,
|
||||
send: Union[Send, Sequence[Send]] = (),
|
||||
) -> None:
|
||||
self.update = update
|
||||
self.send = send
|
||||
|
||||
@property
|
||||
def __all_slots__(self) -> set[str]:
|
||||
# get all slots from mro
|
||||
slots = set()
|
||||
for cls in type(self).__mro__:
|
||||
if ss := getattr(cls, "__slots__", ()):
|
||||
if isinstance(ss, str):
|
||||
slots.add(ss)
|
||||
else:
|
||||
slots.update(ss)
|
||||
return slots
|
||||
update: Optional[dict[str, Any]] = None
|
||||
send: Union[Send, Sequence[Send]] = ()
|
||||
resume: Optional[Union[Any, dict[str, Any]]] = None
|
||||
|
||||
def __repr__(self) -> str:
|
||||
# get all non-None values
|
||||
contents = ", ".join(
|
||||
f"{key}={value!r}"
|
||||
for key in self.__all_slots__
|
||||
if (value := getattr(self, key))
|
||||
for key, value in dataclasses.asdict(self).items()
|
||||
if value
|
||||
)
|
||||
return f"Command({contents})"
|
||||
|
||||
def __eq__(self, value: Any) -> bool:
|
||||
return type(value) is type(self) and all(
|
||||
getattr(self, key) == getattr(value, key) for key in self.__all_slots__
|
||||
)
|
||||
|
||||
def copy(self, **kwargs: Any) -> Self:
|
||||
for slot in self.__all_slots__:
|
||||
kwargs.setdefault(slot, getattr(self, slot))
|
||||
return self.__class__(**kwargs)
|
||||
|
||||
|
||||
StreamChunk = tuple[tuple[str, ...], str, Any]
|
||||
|
||||
@@ -307,3 +293,28 @@ class LoopProtocol:
|
||||
self.store = store
|
||||
self.step = step
|
||||
self.stop = stop
|
||||
|
||||
|
||||
def interrupt(value: Any) -> Any:
|
||||
from langgraph.constants import (
|
||||
CONFIG_KEY_CHECKPOINT_NS,
|
||||
CONFIG_KEY_RESUME_VALUE,
|
||||
MISSING,
|
||||
NS_SEP,
|
||||
)
|
||||
from langgraph.errors import GraphInterrupt
|
||||
from langgraph.utils.config import get_configurable
|
||||
|
||||
conf = get_configurable()
|
||||
if (resume := conf.get(CONFIG_KEY_RESUME_VALUE, MISSING)) and resume is not MISSING:
|
||||
return resume
|
||||
else:
|
||||
raise GraphInterrupt(
|
||||
(
|
||||
Interrupt(
|
||||
value=value,
|
||||
resumable=True,
|
||||
ns=cast(str, conf[CONFIG_KEY_CHECKPOINT_NS]).split(NS_SEP),
|
||||
),
|
||||
)
|
||||
)
|
||||
@@ -1,3 +1,5 @@
|
||||
import asyncio
|
||||
import sys
|
||||
from collections import ChainMap
|
||||
from typing import Any, Optional, Sequence
|
||||
|
||||
@@ -278,7 +280,10 @@ def ensure_config(*configs: Optional[RunnableConfig]) -> RunnableConfig:
|
||||
continue
|
||||
for k, v in config.items():
|
||||
if v is not None and k in CONFIG_KEYS:
|
||||
empty[k] = v # type: ignore[literal-required]
|
||||
if k == CONF:
|
||||
empty[k] = v.copy() # type: ignore[literal-required]
|
||||
else:
|
||||
empty[k] = v # type: ignore[literal-required]
|
||||
for k, v in config.items():
|
||||
if v is not None and k not in CONFIG_KEYS:
|
||||
empty[CONF][k] = v
|
||||
@@ -290,3 +295,18 @@ def ensure_config(*configs: Optional[RunnableConfig]) -> RunnableConfig:
|
||||
):
|
||||
empty["metadata"][key] = value
|
||||
return empty
|
||||
|
||||
|
||||
def get_configurable() -> dict[str, Any]:
|
||||
if sys.version_info < (3, 11):
|
||||
try:
|
||||
if asyncio.current_task():
|
||||
raise RuntimeError(
|
||||
"Python 3.11 or later required to use this in an async context"
|
||||
)
|
||||
except RuntimeError:
|
||||
pass
|
||||
if var_config := var_child_runnable_config.get():
|
||||
return var_config[CONF]
|
||||
else:
|
||||
raise RuntimeError("Called get_configurable outside of a runnable context")
|
||||
Generated
+3
-3
@@ -1359,7 +1359,7 @@ typing-extensions = ">=4.7"
|
||||
|
||||
[[package]]
|
||||
name = "langgraph-checkpoint"
|
||||
version = "2.0.2"
|
||||
version = "2.0.4"
|
||||
description = "Library with base interfaces for LangGraph checkpoint savers."
|
||||
optional = false
|
||||
python-versions = "^3.9.0,<4.0"
|
||||
@@ -1429,7 +1429,7 @@ url = "../checkpoint-sqlite"
|
||||
|
||||
[[package]]
|
||||
name = "langgraph-sdk"
|
||||
version = "0.1.35"
|
||||
version = "0.1.36"
|
||||
description = "SDK for interacting with LangGraph API"
|
||||
optional = false
|
||||
python-versions = "^3.9.0,<4.0"
|
||||
@@ -3425,4 +3425,4 @@ type = ["pytest-mypy"]
|
||||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = ">=3.9.0,<4.0"
|
||||
content-hash = "f0b7800f90041227ba4d7b72482c70fabdbacc95e8a3a784fdebc88721728a91"
|
||||
content-hash = "9bf5668d3f70f3b77457906732404a6401583a5966f70a72ef10a68f2a5b27ad"
|
||||
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "langgraph"
|
||||
version = "0.2.46"
|
||||
version = "0.2.52"
|
||||
description = "Building stateful, multi-actor applications with LLMs"
|
||||
authors = []
|
||||
license = "MIT"
|
||||
@@ -10,7 +10,7 @@ repository = "https://www.github.com/langchain-ai/langgraph"
|
||||
[tool.poetry.dependencies]
|
||||
python = ">=3.9.0,<4.0"
|
||||
langchain-core = ">=0.2.43,<0.4.0,!=0.3.0,!=0.3.1,!=0.3.2,!=0.3.3,!=0.3.4,!=0.3.5,!=0.3.6,!=0.3.7,!=0.3.8,!=0.3.9,!=0.3.10,!=0.3.11,!=0.3.12,!=0.3.13,!=0.3.14"
|
||||
langgraph-checkpoint = "^2.0.0"
|
||||
langgraph-checkpoint = "^2.0.4"
|
||||
langgraph-sdk = "^0.1.32"
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
|
||||
@@ -5108,6 +5108,81 @@
|
||||
|
||||
'''
|
||||
# ---
|
||||
# name: test_send_react_interrupt_control[memory]
|
||||
'''
|
||||
%%{init: {'flowchart': {'curve': 'linear'}}}%%
|
||||
graph TD;
|
||||
__start__([<p>__start__</p>]):::first
|
||||
agent(agent)
|
||||
foo([foo]):::last
|
||||
__start__ --> agent;
|
||||
agent -.-> foo;
|
||||
classDef default fill:#f2f0ff,line-height:1.2
|
||||
classDef first fill-opacity:0
|
||||
classDef last fill:#bfb6fc
|
||||
|
||||
'''
|
||||
# ---
|
||||
# name: test_send_react_interrupt_control[postgres]
|
||||
'''
|
||||
%%{init: {'flowchart': {'curve': 'linear'}}}%%
|
||||
graph TD;
|
||||
__start__([<p>__start__</p>]):::first
|
||||
agent(agent)
|
||||
foo([foo]):::last
|
||||
__start__ --> agent;
|
||||
agent -.-> foo;
|
||||
classDef default fill:#f2f0ff,line-height:1.2
|
||||
classDef first fill-opacity:0
|
||||
classDef last fill:#bfb6fc
|
||||
|
||||
'''
|
||||
# ---
|
||||
# name: test_send_react_interrupt_control[postgres_pipe]
|
||||
'''
|
||||
%%{init: {'flowchart': {'curve': 'linear'}}}%%
|
||||
graph TD;
|
||||
__start__([<p>__start__</p>]):::first
|
||||
agent(agent)
|
||||
foo([foo]):::last
|
||||
__start__ --> agent;
|
||||
agent -.-> foo;
|
||||
classDef default fill:#f2f0ff,line-height:1.2
|
||||
classDef first fill-opacity:0
|
||||
classDef last fill:#bfb6fc
|
||||
|
||||
'''
|
||||
# ---
|
||||
# name: test_send_react_interrupt_control[postgres_pool]
|
||||
'''
|
||||
%%{init: {'flowchart': {'curve': 'linear'}}}%%
|
||||
graph TD;
|
||||
__start__([<p>__start__</p>]):::first
|
||||
agent(agent)
|
||||
foo([foo]):::last
|
||||
__start__ --> agent;
|
||||
agent -.-> foo;
|
||||
classDef default fill:#f2f0ff,line-height:1.2
|
||||
classDef first fill-opacity:0
|
||||
classDef last fill:#bfb6fc
|
||||
|
||||
'''
|
||||
# ---
|
||||
# name: test_send_react_interrupt_control[sqlite]
|
||||
'''
|
||||
%%{init: {'flowchart': {'curve': 'linear'}}}%%
|
||||
graph TD;
|
||||
__start__([<p>__start__</p>]):::first
|
||||
agent(agent)
|
||||
foo([foo]):::last
|
||||
__start__ --> agent;
|
||||
agent -.-> foo;
|
||||
classDef default fill:#f2f0ff,line-height:1.2
|
||||
classDef first fill-opacity:0
|
||||
classDef last fill:#bfb6fc
|
||||
|
||||
'''
|
||||
# ---
|
||||
# name: test_simple_multi_edge
|
||||
'''
|
||||
graph TD;
|
||||
|
||||
@@ -1302,6 +1302,81 @@
|
||||
+---------+
|
||||
'''
|
||||
# ---
|
||||
# name: test_send_react_interrupt_control[memory]
|
||||
'''
|
||||
%%{init: {'flowchart': {'curve': 'linear'}}}%%
|
||||
graph TD;
|
||||
__start__([<p>__start__</p>]):::first
|
||||
agent(agent)
|
||||
foo([foo]):::last
|
||||
__start__ --> agent;
|
||||
agent -.-> foo;
|
||||
classDef default fill:#f2f0ff,line-height:1.2
|
||||
classDef first fill-opacity:0
|
||||
classDef last fill:#bfb6fc
|
||||
|
||||
'''
|
||||
# ---
|
||||
# name: test_send_react_interrupt_control[postgres_aio]
|
||||
'''
|
||||
%%{init: {'flowchart': {'curve': 'linear'}}}%%
|
||||
graph TD;
|
||||
__start__([<p>__start__</p>]):::first
|
||||
agent(agent)
|
||||
foo([foo]):::last
|
||||
__start__ --> agent;
|
||||
agent -.-> foo;
|
||||
classDef default fill:#f2f0ff,line-height:1.2
|
||||
classDef first fill-opacity:0
|
||||
classDef last fill:#bfb6fc
|
||||
|
||||
'''
|
||||
# ---
|
||||
# name: test_send_react_interrupt_control[postgres_aio_pipe]
|
||||
'''
|
||||
%%{init: {'flowchart': {'curve': 'linear'}}}%%
|
||||
graph TD;
|
||||
__start__([<p>__start__</p>]):::first
|
||||
agent(agent)
|
||||
foo([foo]):::last
|
||||
__start__ --> agent;
|
||||
agent -.-> foo;
|
||||
classDef default fill:#f2f0ff,line-height:1.2
|
||||
classDef first fill-opacity:0
|
||||
classDef last fill:#bfb6fc
|
||||
|
||||
'''
|
||||
# ---
|
||||
# name: test_send_react_interrupt_control[postgres_aio_pool]
|
||||
'''
|
||||
%%{init: {'flowchart': {'curve': 'linear'}}}%%
|
||||
graph TD;
|
||||
__start__([<p>__start__</p>]):::first
|
||||
agent(agent)
|
||||
foo([foo]):::last
|
||||
__start__ --> agent;
|
||||
agent -.-> foo;
|
||||
classDef default fill:#f2f0ff,line-height:1.2
|
||||
classDef first fill-opacity:0
|
||||
classDef last fill:#bfb6fc
|
||||
|
||||
'''
|
||||
# ---
|
||||
# name: test_send_react_interrupt_control[sqlite_aio]
|
||||
'''
|
||||
%%{init: {'flowchart': {'curve': 'linear'}}}%%
|
||||
graph TD;
|
||||
__start__([<p>__start__</p>]):::first
|
||||
agent(agent)
|
||||
foo([foo]):::last
|
||||
__start__ --> agent;
|
||||
agent -.-> foo;
|
||||
classDef default fill:#f2f0ff,line-height:1.2
|
||||
classDef first fill-opacity:0
|
||||
classDef last fill:#bfb6fc
|
||||
|
||||
'''
|
||||
# ---
|
||||
# name: test_weather_subgraph[duckdb_aio]
|
||||
'''
|
||||
%%{init: {'flowchart': {'curve': 'linear'}}}%%
|
||||
|
||||
@@ -327,7 +327,6 @@ async def awith_store(store_name: Optional[str]) -> AsyncIterator[BaseStore]:
|
||||
ALL_CHECKPOINTERS_SYNC = [
|
||||
"memory",
|
||||
"sqlite",
|
||||
"duckdb",
|
||||
"postgres",
|
||||
"postgres_pipe",
|
||||
"postgres_pool",
|
||||
@@ -335,7 +334,6 @@ ALL_CHECKPOINTERS_SYNC = [
|
||||
ALL_CHECKPOINTERS_ASYNC = [
|
||||
"memory",
|
||||
"sqlite_aio",
|
||||
"duckdb_aio",
|
||||
"postgres_aio",
|
||||
"postgres_aio_pipe",
|
||||
"postgres_aio_pool",
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import asyncio
|
||||
import os
|
||||
import tempfile
|
||||
from collections import defaultdict
|
||||
from functools import partial
|
||||
from typing import Any, Optional
|
||||
|
||||
from langchain_core.runnables import RunnableConfig
|
||||
@@ -12,7 +15,7 @@ from langgraph.checkpoint.base import (
|
||||
SerializerProtocol,
|
||||
copy_checkpoint,
|
||||
)
|
||||
from langgraph.checkpoint.memory import MemorySaver
|
||||
from langgraph.checkpoint.memory import MemorySaver, PersistentDict
|
||||
|
||||
|
||||
class NoopSerializer(SerializerProtocol):
|
||||
@@ -32,9 +35,13 @@ class MemorySaverAssertImmutable(MemorySaver):
|
||||
serde: Optional[SerializerProtocol] = None,
|
||||
put_sleep: Optional[float] = None,
|
||||
) -> None:
|
||||
super().__init__(serde=serde)
|
||||
_, filename = tempfile.mkstemp()
|
||||
super().__init__(
|
||||
serde=serde, factory=partial(PersistentDict, filename=filename)
|
||||
)
|
||||
self.storage_for_copies = defaultdict(lambda: defaultdict(dict))
|
||||
self.put_sleep = put_sleep
|
||||
self.stack.callback(os.remove, filename)
|
||||
|
||||
def put(
|
||||
self,
|
||||
|
||||
@@ -11,13 +11,21 @@ def test_prepare_next_tasks() -> None:
|
||||
with ChannelsManager({}, checkpoint, config) as (channels, managed):
|
||||
assert (
|
||||
prepare_next_tasks(
|
||||
checkpoint, processes, channels, managed, config, 0, for_execution=False
|
||||
checkpoint,
|
||||
{},
|
||||
processes,
|
||||
channels,
|
||||
managed,
|
||||
config,
|
||||
0,
|
||||
for_execution=False,
|
||||
)
|
||||
== {}
|
||||
)
|
||||
assert (
|
||||
prepare_next_tasks(
|
||||
checkpoint,
|
||||
{},
|
||||
processes,
|
||||
channels,
|
||||
managed,
|
||||
|
||||
+911
-243
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -19,7 +19,7 @@ test:
|
||||
exit $$EXIT_CODE
|
||||
|
||||
test_watch:
|
||||
make start-services && poetry run ptw . -- $(TEST_PATH); \
|
||||
make start-services && poetry run ptw . -- -x $(TEST_PATH); \
|
||||
EXIT_CODE=$$?; \
|
||||
make stop-services; \
|
||||
exit $$EXIT_CODE
|
||||
|
||||
@@ -38,7 +38,7 @@ from langgraph.scheduler.kafka.types import (
|
||||
Sendable,
|
||||
Topics,
|
||||
)
|
||||
from langgraph.types import LoopProtocol, RetryPolicy
|
||||
from langgraph.types import LoopProtocol, PregelExecutableTask, RetryPolicy
|
||||
from langgraph.utils.config import patch_configurable
|
||||
|
||||
|
||||
@@ -198,6 +198,7 @@ class AsyncKafkaExecutor(AbstractAsyncContextManager):
|
||||
msg["task"]["path"],
|
||||
msg["task"]["id"],
|
||||
checkpoint=saved.checkpoint,
|
||||
pending_writes=saved.pending_writes or [],
|
||||
processes=graph.nodes,
|
||||
channels=channels,
|
||||
managed=managed,
|
||||
@@ -211,6 +212,7 @@ class AsyncKafkaExecutor(AbstractAsyncContextManager):
|
||||
runner = PregelRunner(
|
||||
submit=submit,
|
||||
put_writes=partial(self._put_writes, submit, msg["config"]),
|
||||
schedule_task=self._schedule_task,
|
||||
)
|
||||
async for _ in runner.atick([task], reraise=False):
|
||||
pass
|
||||
@@ -239,6 +241,14 @@ class AsyncKafkaExecutor(AbstractAsyncContextManager):
|
||||
)
|
||||
await fut
|
||||
|
||||
def _schedule_task(
|
||||
self,
|
||||
task: PregelExecutableTask,
|
||||
idx: int,
|
||||
) -> None:
|
||||
# will be scheduled by orchestrator when executor finishes
|
||||
pass
|
||||
|
||||
def _put_writes(
|
||||
self,
|
||||
submit: Submit,
|
||||
@@ -400,6 +410,7 @@ class KafkaExecutor(AbstractContextManager):
|
||||
msg["task"]["path"],
|
||||
msg["task"]["id"],
|
||||
checkpoint=saved.checkpoint,
|
||||
pending_writes=saved.pending_writes or [],
|
||||
processes=graph.nodes,
|
||||
channels=channels,
|
||||
managed=managed,
|
||||
@@ -412,6 +423,7 @@ class KafkaExecutor(AbstractContextManager):
|
||||
runner = PregelRunner(
|
||||
submit=submit,
|
||||
put_writes=partial(self._put_writes, submit, msg["config"]),
|
||||
schedule_task=self._schedule_task,
|
||||
)
|
||||
for _ in runner.tick([task], reraise=False):
|
||||
pass
|
||||
@@ -440,6 +452,14 @@ class KafkaExecutor(AbstractContextManager):
|
||||
)
|
||||
fut.result()
|
||||
|
||||
def _schedule_task(
|
||||
self,
|
||||
task: PregelExecutableTask,
|
||||
idx: int,
|
||||
) -> None:
|
||||
# will be scheduled by orchestrator when executor finishes
|
||||
pass
|
||||
|
||||
def _put_writes(
|
||||
self,
|
||||
submit: Submit,
|
||||
|
||||
@@ -161,18 +161,18 @@ class AsyncKafkaOrchestrator(AbstractAsyncContextManager):
|
||||
specs=graph.channels,
|
||||
output_keys=graph.output_channels,
|
||||
stream_keys=graph.stream_channels,
|
||||
interrupt_after=graph.interrupt_after_nodes,
|
||||
interrupt_before=graph.interrupt_before_nodes,
|
||||
check_subgraphs=False,
|
||||
) as loop:
|
||||
if loop.tick(
|
||||
input_keys=graph.input_channels,
|
||||
interrupt_after=graph.interrupt_after_nodes,
|
||||
interrupt_before=graph.interrupt_before_nodes,
|
||||
):
|
||||
if loop.tick(input_keys=graph.input_channels):
|
||||
# wait for checkpoint to be saved
|
||||
if hasattr(loop, "_put_checkpoint_fut"):
|
||||
await loop._put_checkpoint_fut
|
||||
# schedule any new tasks
|
||||
if new_tasks := [t for t in loop.tasks.values() if not t.scheduled]:
|
||||
if new_tasks := [
|
||||
t for t in loop.tasks.values() if not t.scheduled and not t.writes
|
||||
]:
|
||||
# send messages to executor
|
||||
futures = await asyncio.gather(
|
||||
*(
|
||||
@@ -351,18 +351,18 @@ class KafkaOrchestrator(AbstractContextManager):
|
||||
specs=graph.channels,
|
||||
output_keys=graph.output_channels,
|
||||
stream_keys=graph.stream_channels,
|
||||
interrupt_after=graph.interrupt_after_nodes,
|
||||
interrupt_before=graph.interrupt_before_nodes,
|
||||
check_subgraphs=False,
|
||||
) as loop:
|
||||
if loop.tick(
|
||||
input_keys=graph.input_channels,
|
||||
interrupt_after=graph.interrupt_after_nodes,
|
||||
interrupt_before=graph.interrupt_before_nodes,
|
||||
):
|
||||
if loop.tick(input_keys=graph.input_channels):
|
||||
# wait for checkpoint to be saved
|
||||
if hasattr(loop, "_put_checkpoint_fut"):
|
||||
loop._put_checkpoint_fut.result()
|
||||
# schedule any new tasks
|
||||
if new_tasks := [t for t in loop.tasks.values() if not t.scheduled]:
|
||||
if new_tasks := [
|
||||
t for t in loop.tasks.values() if not t.scheduled and not t.writes
|
||||
]:
|
||||
# send messages to executor
|
||||
futures = [
|
||||
self.producer.send(
|
||||
|
||||
@@ -24,8 +24,8 @@ class MessageToOrchestrator(TypedDict):
|
||||
|
||||
|
||||
class ExecutorTask(TypedDict):
|
||||
id: str
|
||||
path: tuple[str, ...]
|
||||
id: Optional[str]
|
||||
path: tuple[Union[str, int], ...]
|
||||
|
||||
|
||||
class MessageToExecutor(TypedDict):
|
||||
|
||||
@@ -0,0 +1,208 @@
|
||||
import operator
|
||||
from typing import (
|
||||
Annotated,
|
||||
Literal,
|
||||
Union,
|
||||
)
|
||||
|
||||
import pytest
|
||||
from aiokafka import AIOKafkaProducer
|
||||
|
||||
from langgraph.checkpoint.base import BaseCheckpointSaver
|
||||
from langgraph.constants import FF_SEND_V2, START
|
||||
from langgraph.errors import NodeInterrupt
|
||||
from langgraph.graph.state import CompiledStateGraph, GraphCommand, StateGraph
|
||||
from langgraph.scheduler.kafka import serde
|
||||
from langgraph.scheduler.kafka.types import MessageToOrchestrator, Topics
|
||||
from langgraph.types import Send
|
||||
from tests.any import AnyDict
|
||||
from tests.drain import drain_topics_async
|
||||
|
||||
pytestmark = pytest.mark.anyio
|
||||
|
||||
|
||||
def mk_push_graph(
|
||||
checkpointer: BaseCheckpointSaver,
|
||||
) -> CompiledStateGraph:
|
||||
# copied from test_send_dedupe_on_resume
|
||||
|
||||
class InterruptOnce:
|
||||
ticks: int = 0
|
||||
|
||||
def __call__(self, state):
|
||||
self.ticks += 1
|
||||
if self.ticks == 1:
|
||||
raise NodeInterrupt("Bahh")
|
||||
return ["|".join(("flaky", str(state)))]
|
||||
|
||||
class Node:
|
||||
def __init__(self, name: str):
|
||||
self.name = name
|
||||
self.ticks = 0
|
||||
self.__name__ = name
|
||||
|
||||
def __call__(self, state):
|
||||
self.ticks += 1
|
||||
update = (
|
||||
[self.name]
|
||||
if isinstance(state, list)
|
||||
else ["|".join((self.name, str(state)))]
|
||||
)
|
||||
if isinstance(state, GraphCommand):
|
||||
return state.copy(update=update)
|
||||
else:
|
||||
return update
|
||||
|
||||
def send_for_fun(state):
|
||||
return [
|
||||
Send("2", GraphCommand(send=Send("2", 3))),
|
||||
Send("2", GraphCommand(send=Send("flaky", 4))),
|
||||
"3.1",
|
||||
]
|
||||
|
||||
def route_to_three(state) -> Literal["3"]:
|
||||
return "3"
|
||||
|
||||
builder = StateGraph(Annotated[list, operator.add])
|
||||
builder.add_node(Node("1"))
|
||||
builder.add_node(Node("2"))
|
||||
builder.add_node(Node("3"))
|
||||
builder.add_node(Node("3.1"))
|
||||
builder.add_node("flaky", InterruptOnce())
|
||||
builder.add_edge(START, "1")
|
||||
builder.add_conditional_edges("1", send_for_fun)
|
||||
builder.add_conditional_edges("2", route_to_three)
|
||||
|
||||
return builder.compile(checkpointer=checkpointer)
|
||||
|
||||
|
||||
async def test_push_graph(topics: Topics, acheckpointer: BaseCheckpointSaver) -> None:
|
||||
if not FF_SEND_V2:
|
||||
pytest.skip("Test requires FF_SEND_V2")
|
||||
|
||||
input = ["0"]
|
||||
config = {"configurable": {"thread_id": "1"}}
|
||||
graph = mk_push_graph(acheckpointer)
|
||||
graph_compare = mk_push_graph(acheckpointer)
|
||||
|
||||
# start a new run
|
||||
async with AIOKafkaProducer(value_serializer=serde.dumps) as producer:
|
||||
await producer.send_and_wait(
|
||||
topics.orchestrator,
|
||||
MessageToOrchestrator(input=input, config=config),
|
||||
)
|
||||
|
||||
# drain topics
|
||||
orch_msgs, exec_msgs = await drain_topics_async(topics, graph)
|
||||
|
||||
# check state
|
||||
state = await graph.aget_state(config)
|
||||
assert all(not t.error for t in state.tasks)
|
||||
assert state.next == ("flaky",)
|
||||
assert (
|
||||
state.values
|
||||
== await graph_compare.ainvoke(input, {"configurable": {"thread_id": "2"}})
|
||||
== [
|
||||
"0",
|
||||
"1",
|
||||
"2|Control(send=Send(node='2', arg=3))",
|
||||
"2|Control(send=Send(node='flaky', arg=4))",
|
||||
"2|3",
|
||||
]
|
||||
)
|
||||
|
||||
# check history
|
||||
history = [c async for c in graph.aget_state_history(config)]
|
||||
assert len(history) == 2
|
||||
|
||||
# check messages
|
||||
assert orch_msgs == [MessageToOrchestrator(input=input, config=config)] + [
|
||||
{
|
||||
"config": {
|
||||
"callbacks": None,
|
||||
"configurable": {
|
||||
"__pregel_ensure_latest": True,
|
||||
"__pregel_dedupe_tasks": True,
|
||||
"__pregel_resuming": False,
|
||||
"checkpoint_id": c.config["configurable"]["checkpoint_id"],
|
||||
"checkpoint_ns": "",
|
||||
"thread_id": "1",
|
||||
},
|
||||
"metadata": AnyDict(),
|
||||
"recursion_limit": 25,
|
||||
"tags": [],
|
||||
},
|
||||
"input": None,
|
||||
"finally_send": None,
|
||||
}
|
||||
for c in reversed(history)
|
||||
for _ in c.tasks
|
||||
]
|
||||
assert exec_msgs == [
|
||||
{
|
||||
"config": {
|
||||
"callbacks": None,
|
||||
"configurable": {
|
||||
"__pregel_ensure_latest": True,
|
||||
"__pregel_dedupe_tasks": True,
|
||||
"__pregel_resuming": False,
|
||||
"checkpoint_id": c.config["configurable"]["checkpoint_id"],
|
||||
"checkpoint_ns": "",
|
||||
"thread_id": "1",
|
||||
},
|
||||
"metadata": AnyDict(),
|
||||
"recursion_limit": 25,
|
||||
"tags": [],
|
||||
},
|
||||
"task": {
|
||||
"id": t.id,
|
||||
"path": _convert_path(t.path),
|
||||
},
|
||||
"finally_send": None,
|
||||
}
|
||||
for c in reversed(history)
|
||||
for t in c.tasks
|
||||
]
|
||||
|
||||
# resume the thread
|
||||
async with AIOKafkaProducer(value_serializer=serde.dumps) as producer:
|
||||
await producer.send_and_wait(
|
||||
topics.orchestrator,
|
||||
MessageToOrchestrator(input=None, config=config),
|
||||
)
|
||||
|
||||
orch_msgs, exec_msgs = await drain_topics_async(topics, graph)
|
||||
|
||||
# check final state
|
||||
state = await graph.aget_state(config)
|
||||
assert state.next == ()
|
||||
assert (
|
||||
state.values
|
||||
== await graph_compare.ainvoke(None, {"configurable": {"thread_id": "2"}})
|
||||
== [
|
||||
"0",
|
||||
"1",
|
||||
"2|Control(send=Send(node='2', arg=3))",
|
||||
"2|Control(send=Send(node='flaky', arg=4))",
|
||||
"2|3",
|
||||
"flaky|4",
|
||||
"3",
|
||||
"3.1",
|
||||
]
|
||||
)
|
||||
|
||||
# check history
|
||||
history = [c async for c in graph.aget_state_history(config)]
|
||||
assert len(history) == 4
|
||||
|
||||
# check executions
|
||||
# node "2" doesn't get called again, as we recover writes saved before
|
||||
assert graph.builder.nodes["2"].runnable.func.ticks == 3
|
||||
# node "flaky" gets called again, as it was interrupted
|
||||
assert graph.builder.nodes["flaky"].runnable.func.ticks == 2
|
||||
|
||||
|
||||
def _convert_path(
|
||||
path: tuple[Union[str, int, tuple], ...],
|
||||
) -> list[Union[str, int, list]]:
|
||||
return list(_convert_path(p) if isinstance(p, tuple) else p for p in path)
|
||||
@@ -0,0 +1,210 @@
|
||||
import operator
|
||||
from typing import (
|
||||
Annotated,
|
||||
Literal,
|
||||
Union,
|
||||
)
|
||||
|
||||
import pytest
|
||||
|
||||
from langgraph.checkpoint.base import BaseCheckpointSaver
|
||||
from langgraph.constants import FF_SEND_V2, START
|
||||
from langgraph.errors import NodeInterrupt
|
||||
from langgraph.graph.state import CompiledStateGraph, GraphCommand, StateGraph
|
||||
from langgraph.scheduler.kafka import serde
|
||||
from langgraph.scheduler.kafka.default_sync import DefaultProducer
|
||||
from langgraph.scheduler.kafka.types import MessageToOrchestrator, Topics
|
||||
from langgraph.types import Send
|
||||
from tests.any import AnyDict
|
||||
from tests.drain import drain_topics
|
||||
|
||||
pytestmark = pytest.mark.anyio
|
||||
|
||||
|
||||
def mk_push_graph(
|
||||
checkpointer: BaseCheckpointSaver,
|
||||
) -> CompiledStateGraph:
|
||||
# copied from test_send_dedupe_on_resume
|
||||
|
||||
class InterruptOnce:
|
||||
ticks: int = 0
|
||||
|
||||
def __call__(self, state):
|
||||
self.ticks += 1
|
||||
if self.ticks == 1:
|
||||
raise NodeInterrupt("Bahh")
|
||||
return ["|".join(("flaky", str(state)))]
|
||||
|
||||
class Node:
|
||||
def __init__(self, name: str):
|
||||
self.name = name
|
||||
self.ticks = 0
|
||||
self.__name__ = name
|
||||
|
||||
def __call__(self, state):
|
||||
self.ticks += 1
|
||||
update = (
|
||||
[self.name]
|
||||
if isinstance(state, list)
|
||||
else ["|".join((self.name, str(state)))]
|
||||
)
|
||||
if isinstance(state, GraphCommand):
|
||||
return state.copy(update=update)
|
||||
else:
|
||||
return update
|
||||
|
||||
def send_for_fun(state):
|
||||
return [
|
||||
Send("2", GraphCommand(send=Send("2", 3))),
|
||||
Send("2", GraphCommand(send=Send("flaky", 4))),
|
||||
"3.1",
|
||||
]
|
||||
|
||||
def route_to_three(state) -> Literal["3"]:
|
||||
return "3"
|
||||
|
||||
builder = StateGraph(Annotated[list, operator.add])
|
||||
builder.add_node(Node("1"))
|
||||
builder.add_node(Node("2"))
|
||||
builder.add_node(Node("3"))
|
||||
builder.add_node(Node("3.1"))
|
||||
builder.add_node("flaky", InterruptOnce())
|
||||
builder.add_edge(START, "1")
|
||||
builder.add_conditional_edges("1", send_for_fun)
|
||||
builder.add_conditional_edges("2", route_to_three)
|
||||
|
||||
return builder.compile(checkpointer=checkpointer)
|
||||
|
||||
|
||||
def test_push_graph(topics: Topics, acheckpointer: BaseCheckpointSaver) -> None:
|
||||
if not FF_SEND_V2:
|
||||
pytest.skip("Test requires FF_SEND_V2")
|
||||
|
||||
input = ["0"]
|
||||
config = {"configurable": {"thread_id": "1"}}
|
||||
graph = mk_push_graph(acheckpointer)
|
||||
graph_compare = mk_push_graph(acheckpointer)
|
||||
|
||||
# start a new run
|
||||
with DefaultProducer() as producer:
|
||||
producer.send(
|
||||
topics.orchestrator,
|
||||
value=serde.dumps(MessageToOrchestrator(input=input, config=config)),
|
||||
)
|
||||
producer.flush()
|
||||
|
||||
# drain topics
|
||||
orch_msgs, exec_msgs = drain_topics(topics, graph)
|
||||
|
||||
# check state
|
||||
state = graph.get_state(config)
|
||||
assert all(not t.error for t in state.tasks)
|
||||
assert state.next == ("flaky",)
|
||||
assert (
|
||||
state.values
|
||||
== graph_compare.invoke(input, {"configurable": {"thread_id": "2"}})
|
||||
== [
|
||||
"0",
|
||||
"1",
|
||||
"2|Control(send=Send(node='2', arg=3))",
|
||||
"2|Control(send=Send(node='flaky', arg=4))",
|
||||
"2|3",
|
||||
]
|
||||
)
|
||||
|
||||
# check history
|
||||
history = [c for c in graph.get_state_history(config)]
|
||||
assert len(history) == 2
|
||||
|
||||
# check messages
|
||||
assert orch_msgs == [MessageToOrchestrator(input=input, config=config)] + [
|
||||
{
|
||||
"config": {
|
||||
"callbacks": None,
|
||||
"configurable": {
|
||||
"__pregel_ensure_latest": True,
|
||||
"__pregel_dedupe_tasks": True,
|
||||
"__pregel_resuming": False,
|
||||
"checkpoint_id": c.config["configurable"]["checkpoint_id"],
|
||||
"checkpoint_ns": "",
|
||||
"thread_id": "1",
|
||||
},
|
||||
"metadata": AnyDict(),
|
||||
"recursion_limit": 25,
|
||||
"tags": [],
|
||||
},
|
||||
"input": None,
|
||||
"finally_send": None,
|
||||
}
|
||||
for c in reversed(history)
|
||||
for _ in c.tasks
|
||||
]
|
||||
assert exec_msgs == [
|
||||
{
|
||||
"config": {
|
||||
"callbacks": None,
|
||||
"configurable": {
|
||||
"__pregel_ensure_latest": True,
|
||||
"__pregel_dedupe_tasks": True,
|
||||
"__pregel_resuming": False,
|
||||
"checkpoint_id": c.config["configurable"]["checkpoint_id"],
|
||||
"checkpoint_ns": "",
|
||||
"thread_id": "1",
|
||||
},
|
||||
"metadata": AnyDict(),
|
||||
"recursion_limit": 25,
|
||||
"tags": [],
|
||||
},
|
||||
"task": {
|
||||
"id": t.id,
|
||||
"path": _convert_path(t.path),
|
||||
},
|
||||
"finally_send": None,
|
||||
}
|
||||
for c in reversed(history)
|
||||
for t in c.tasks
|
||||
]
|
||||
|
||||
# resume the thread
|
||||
with DefaultProducer() as producer:
|
||||
producer.send(
|
||||
topics.orchestrator,
|
||||
value=serde.dumps(MessageToOrchestrator(input=None, config=config)),
|
||||
)
|
||||
producer.flush()
|
||||
|
||||
orch_msgs, exec_msgs = drain_topics(topics, graph)
|
||||
|
||||
# check final state
|
||||
state = graph.get_state(config)
|
||||
assert state.next == ()
|
||||
assert (
|
||||
state.values
|
||||
== graph_compare.invoke(None, {"configurable": {"thread_id": "2"}})
|
||||
== [
|
||||
"0",
|
||||
"1",
|
||||
"2|Control(send=Send(node='2', arg=3))",
|
||||
"2|Control(send=Send(node='flaky', arg=4))",
|
||||
"2|3",
|
||||
"flaky|4",
|
||||
"3",
|
||||
"3.1",
|
||||
]
|
||||
)
|
||||
|
||||
# check history
|
||||
history = [c for c in graph.get_state_history(config)]
|
||||
assert len(history) == 4
|
||||
|
||||
# check executions
|
||||
# node "2" doesn't get called again, as we recover writes saved before
|
||||
assert graph.builder.nodes["2"].runnable.func.ticks == 3
|
||||
# node "flaky" gets called again, as it was interrupted
|
||||
assert graph.builder.nodes["flaky"].runnable.func.ticks == 2
|
||||
|
||||
|
||||
def _convert_path(
|
||||
path: tuple[Union[str, int, tuple], ...],
|
||||
) -> list[Union[str, int, list]]:
|
||||
return list(_convert_path(p) if isinstance(p, tuple) else p for p in path)
|
||||
@@ -194,8 +194,9 @@ async def test_subgraph_w_interrupt(
|
||||
"__pregel_ensure_latest": True,
|
||||
"__pregel_dedupe_tasks": True,
|
||||
"__pregel_resuming": False,
|
||||
'__pregel_store': None,
|
||||
"__pregel_store": None,
|
||||
"__pregel_task_id": history[0].tasks[0].id,
|
||||
"__pregel_resume_value": None,
|
||||
"checkpoint_id": None,
|
||||
"checkpoint_map": {
|
||||
"": history[0].config["configurable"]["checkpoint_id"]
|
||||
@@ -258,8 +259,9 @@ async def test_subgraph_w_interrupt(
|
||||
"__pregel_ensure_latest": True,
|
||||
"__pregel_dedupe_tasks": True,
|
||||
"__pregel_resuming": False,
|
||||
'__pregel_store': None,
|
||||
"__pregel_store": None,
|
||||
"__pregel_task_id": history[0].tasks[0].id,
|
||||
"__pregel_resume_value": None,
|
||||
"checkpoint_id": c.config["configurable"]["checkpoint_id"],
|
||||
"checkpoint_map": {
|
||||
"": history[0].config["configurable"]["checkpoint_id"]
|
||||
@@ -352,8 +354,9 @@ async def test_subgraph_w_interrupt(
|
||||
"__pregel_ensure_latest": True,
|
||||
"__pregel_dedupe_tasks": True,
|
||||
"__pregel_resuming": False,
|
||||
'__pregel_store': None,
|
||||
"__pregel_store": None,
|
||||
"__pregel_task_id": history[0].tasks[0].id,
|
||||
"__pregel_resume_value": None,
|
||||
"checkpoint_id": c.config["configurable"]["checkpoint_id"],
|
||||
"checkpoint_map": {
|
||||
"": history[0].config["configurable"]["checkpoint_id"]
|
||||
@@ -456,8 +459,9 @@ async def test_subgraph_w_interrupt(
|
||||
"__pregel_ensure_latest": True,
|
||||
"__pregel_dedupe_tasks": True,
|
||||
"__pregel_resuming": True,
|
||||
'__pregel_store': None,
|
||||
"__pregel_store": None,
|
||||
"__pregel_task_id": history[1].tasks[0].id,
|
||||
"__pregel_resume_value": None,
|
||||
"checkpoint_id": None,
|
||||
"checkpoint_map": {
|
||||
"": history[1].config["configurable"]["checkpoint_id"]
|
||||
@@ -515,8 +519,9 @@ async def test_subgraph_w_interrupt(
|
||||
"__pregel_ensure_latest": True,
|
||||
"__pregel_dedupe_tasks": True,
|
||||
"__pregel_resuming": True,
|
||||
'__pregel_store': None,
|
||||
"__pregel_store": None,
|
||||
"__pregel_task_id": history[1].tasks[0].id,
|
||||
"__pregel_resume_value": None,
|
||||
"checkpoint_id": c.config["configurable"]["checkpoint_id"],
|
||||
"checkpoint_map": {
|
||||
"": history[1].config["configurable"]["checkpoint_id"]
|
||||
@@ -630,8 +635,9 @@ async def test_subgraph_w_interrupt(
|
||||
"__pregel_ensure_latest": True,
|
||||
"__pregel_dedupe_tasks": True,
|
||||
"__pregel_resuming": True,
|
||||
'__pregel_store': None,
|
||||
"__pregel_store": None,
|
||||
"__pregel_task_id": history[1].tasks[0].id,
|
||||
"__pregel_resume_value": None,
|
||||
"checkpoint_id": c.config["configurable"]["checkpoint_id"],
|
||||
"checkpoint_map": {
|
||||
"": history[1].config["configurable"]["checkpoint_id"]
|
||||
|
||||
@@ -193,8 +193,9 @@ def test_subgraph_w_interrupt(
|
||||
"__pregel_ensure_latest": True,
|
||||
"__pregel_dedupe_tasks": True,
|
||||
"__pregel_resuming": False,
|
||||
'__pregel_store': None,
|
||||
"__pregel_store": None,
|
||||
"__pregel_task_id": history[0].tasks[0].id,
|
||||
"__pregel_resume_value": None,
|
||||
"checkpoint_id": None,
|
||||
"checkpoint_map": {
|
||||
"": history[0].config["configurable"]["checkpoint_id"]
|
||||
@@ -255,10 +256,11 @@ def test_subgraph_w_interrupt(
|
||||
"__pregel_read": None,
|
||||
"__pregel_send": None,
|
||||
"__pregel_ensure_latest": True,
|
||||
'__pregel_store': None,
|
||||
"__pregel_store": None,
|
||||
"__pregel_dedupe_tasks": True,
|
||||
"__pregel_resuming": False,
|
||||
"__pregel_task_id": history[0].tasks[0].id,
|
||||
"__pregel_resume_value": None,
|
||||
"checkpoint_id": c.config["configurable"]["checkpoint_id"],
|
||||
"checkpoint_map": {
|
||||
"": history[0].config["configurable"]["checkpoint_id"]
|
||||
@@ -350,9 +352,10 @@ def test_subgraph_w_interrupt(
|
||||
"__pregel_send": None,
|
||||
"__pregel_ensure_latest": True,
|
||||
"__pregel_dedupe_tasks": True,
|
||||
'__pregel_store': None,
|
||||
"__pregel_store": None,
|
||||
"__pregel_resuming": False,
|
||||
"__pregel_task_id": history[0].tasks[0].id,
|
||||
"__pregel_resume_value": None,
|
||||
"checkpoint_id": c.config["configurable"]["checkpoint_id"],
|
||||
"checkpoint_map": {
|
||||
"": history[0].config["configurable"]["checkpoint_id"]
|
||||
@@ -453,9 +456,10 @@ def test_subgraph_w_interrupt(
|
||||
"__pregel_send": None,
|
||||
"__pregel_ensure_latest": True,
|
||||
"__pregel_dedupe_tasks": True,
|
||||
'__pregel_store': None,
|
||||
"__pregel_store": None,
|
||||
"__pregel_resuming": True,
|
||||
"__pregel_task_id": history[1].tasks[0].id,
|
||||
"__pregel_resume_value": None,
|
||||
"checkpoint_id": None,
|
||||
"checkpoint_map": {
|
||||
"": history[1].config["configurable"]["checkpoint_id"]
|
||||
@@ -512,9 +516,10 @@ def test_subgraph_w_interrupt(
|
||||
"__pregel_send": None,
|
||||
"__pregel_ensure_latest": True,
|
||||
"__pregel_dedupe_tasks": True,
|
||||
'__pregel_store': None,
|
||||
"__pregel_store": None,
|
||||
"__pregel_resuming": True,
|
||||
"__pregel_task_id": history[1].tasks[0].id,
|
||||
"__pregel_resume_value": None,
|
||||
"checkpoint_id": c.config["configurable"]["checkpoint_id"],
|
||||
"checkpoint_map": {
|
||||
"": history[1].config["configurable"]["checkpoint_id"]
|
||||
@@ -628,8 +633,9 @@ def test_subgraph_w_interrupt(
|
||||
"__pregel_ensure_latest": True,
|
||||
"__pregel_dedupe_tasks": True,
|
||||
"__pregel_resuming": True,
|
||||
'__pregel_store': None,
|
||||
"__pregel_store": None,
|
||||
"__pregel_task_id": history[1].tasks[0].id,
|
||||
"__pregel_resume_value": None,
|
||||
"checkpoint_id": c.config["configurable"]["checkpoint_id"],
|
||||
"checkpoint_map": {
|
||||
"": history[1].config["configurable"]["checkpoint_id"]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@langchain/langgraph-sdk",
|
||||
"version": "0.0.23",
|
||||
"version": "0.0.26",
|
||||
"description": "Client library for interacting with the LangGraph API",
|
||||
"type": "module",
|
||||
"packageManager": "yarn@1.22.19",
|
||||
|
||||
@@ -34,6 +34,35 @@ import {
|
||||
} from "./types.js";
|
||||
import { mergeSignals } from "./utils/signals.js";
|
||||
|
||||
/**
|
||||
* Get the API key from the environment.
|
||||
* Precedence:
|
||||
* 1. explicit argument
|
||||
* 2. LANGGRAPH_API_KEY
|
||||
* 3. LANGSMITH_API_KEY
|
||||
* 4. LANGCHAIN_API_KEY
|
||||
*
|
||||
* @param apiKey - Optional API key provided as an argument
|
||||
* @returns The API key if found, otherwise undefined
|
||||
*/
|
||||
export function getApiKey(apiKey?: string): string | undefined {
|
||||
if (apiKey) {
|
||||
return apiKey;
|
||||
}
|
||||
|
||||
const prefixes = ["LANGGRAPH", "LANGSMITH", "LANGCHAIN"];
|
||||
|
||||
for (const prefix of prefixes) {
|
||||
const envKey = process.env[`${prefix}_API_KEY`];
|
||||
if (envKey) {
|
||||
// Remove surrounding quotes
|
||||
return envKey.trim().replace(/^["']|["']$/g, "");
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
interface ClientConfig {
|
||||
apiUrl?: string;
|
||||
apiKey?: string;
|
||||
@@ -62,10 +91,12 @@ class BaseClient {
|
||||
|
||||
// default limit being capped by Chrome
|
||||
// https://github.com/nodejs/undici/issues/1373
|
||||
this.apiUrl = config?.apiUrl || "http://localhost:8123";
|
||||
// Regex to remove trailing slash, if present
|
||||
this.apiUrl = config?.apiUrl?.replace(/\/$/, "") || "http://localhost:8123";
|
||||
this.defaultHeaders = config?.defaultHeaders || {};
|
||||
if (config?.apiKey != null) {
|
||||
this.defaultHeaders["X-Api-Key"] = config.apiKey;
|
||||
const apiKey = getApiKey(config?.apiKey);
|
||||
if (apiKey) {
|
||||
this.defaultHeaders["X-Api-Key"] = apiKey;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -693,6 +724,7 @@ export class RunsClient extends BaseClient {
|
||||
}> {
|
||||
const json: Record<string, any> = {
|
||||
input: payload?.input,
|
||||
command: payload?.command,
|
||||
config: payload?.config,
|
||||
metadata: payload?.metadata,
|
||||
stream_mode: payload?.streamMode,
|
||||
@@ -780,6 +812,7 @@ export class RunsClient extends BaseClient {
|
||||
): Promise<Run> {
|
||||
const json: Record<string, any> = {
|
||||
input: payload?.input,
|
||||
command: payload?.command,
|
||||
config: payload?.config,
|
||||
metadata: payload?.metadata,
|
||||
assistant_id: assistantId,
|
||||
@@ -849,6 +882,7 @@ export class RunsClient extends BaseClient {
|
||||
): Promise<ThreadState["values"]> {
|
||||
const json: Record<string, any> = {
|
||||
input: payload?.input,
|
||||
command: payload?.command,
|
||||
config: payload?.config,
|
||||
metadata: payload?.metadata,
|
||||
assistant_id: assistantId,
|
||||
|
||||
@@ -10,10 +10,11 @@ export type {
|
||||
Metadata,
|
||||
Run,
|
||||
Thread,
|
||||
ThreadTask,
|
||||
ThreadState,
|
||||
ThreadStatus,
|
||||
Cron,
|
||||
Checkpoint,
|
||||
} from "./schema.js";
|
||||
|
||||
export type { OnConflictBehavior } from "./types.js";
|
||||
export type { OnConflictBehavior, Command } from "./types.js";
|
||||
@@ -208,8 +208,14 @@ export interface ThreadState<ValuesType = DefaultValues> {
|
||||
export interface ThreadTask {
|
||||
id: string;
|
||||
name: string;
|
||||
result?: unknown;
|
||||
error: Optional<string>;
|
||||
interrupts: Array<{ value: unknown; when: "during" }>;
|
||||
interrupts: Array<{
|
||||
value: unknown;
|
||||
when: "during";
|
||||
resumable: boolean;
|
||||
ns?: string[];
|
||||
}>;
|
||||
checkpoint: Optional<Checkpoint>;
|
||||
state: Optional<ThreadState>;
|
||||
}
|
||||
|
||||
@@ -23,6 +23,28 @@ export type StreamEvent =
|
||||
| "messages/complete"
|
||||
| (string & {});
|
||||
|
||||
export interface Send {
|
||||
node: string;
|
||||
input: Record<string, unknown> | null;
|
||||
}
|
||||
|
||||
export interface Command {
|
||||
/**
|
||||
* An object to update the thread state with.
|
||||
*/
|
||||
update?: Record<string, unknown>;
|
||||
|
||||
/**
|
||||
* The value to return from an `interrupt` function call.
|
||||
*/
|
||||
resume?: unknown;
|
||||
|
||||
/**
|
||||
* A single, or array of `Send` commands to trigger nodes.
|
||||
*/
|
||||
send?: Send | Send[];
|
||||
}
|
||||
|
||||
interface RunsInvokePayload {
|
||||
/**
|
||||
* Input to the run. Pass `null` to resume from the current state of the thread.
|
||||
@@ -107,6 +129,11 @@ interface RunsInvokePayload {
|
||||
* Behavior if the specified run doesn't exist. Defaults to "reject".
|
||||
*/
|
||||
ifNotExists?: "create" | "reject";
|
||||
|
||||
/**
|
||||
* One or more commands to invoke the graph with.
|
||||
*/
|
||||
command?: Command;
|
||||
}
|
||||
|
||||
export interface RunsStreamPayload extends RunsInvokePayload {
|
||||
|
||||
@@ -36,6 +36,7 @@ from langgraph_sdk.schema import (
|
||||
AssistantVersion,
|
||||
CancelAction,
|
||||
Checkpoint,
|
||||
Command,
|
||||
Config,
|
||||
Cron,
|
||||
DisconnectMode,
|
||||
@@ -1174,6 +1175,7 @@ class RunsClient:
|
||||
assistant_id: str,
|
||||
*,
|
||||
input: Optional[dict] = None,
|
||||
command: Optional[Command] = None,
|
||||
stream_mode: Union[StreamMode, Sequence[StreamMode]] = "values",
|
||||
stream_subgraphs: bool = False,
|
||||
metadata: Optional[dict] = None,
|
||||
@@ -1197,6 +1199,7 @@ class RunsClient:
|
||||
assistant_id: str,
|
||||
*,
|
||||
input: Optional[dict] = None,
|
||||
command: Optional[Command] = None,
|
||||
stream_mode: Union[StreamMode, Sequence[StreamMode]] = "values",
|
||||
stream_subgraphs: bool = False,
|
||||
metadata: Optional[dict] = None,
|
||||
@@ -1217,6 +1220,7 @@ class RunsClient:
|
||||
assistant_id: str,
|
||||
*,
|
||||
input: Optional[dict] = None,
|
||||
command: Optional[Command] = None,
|
||||
stream_mode: Union[StreamMode, Sequence[StreamMode]] = "values",
|
||||
stream_subgraphs: bool = False,
|
||||
metadata: Optional[dict] = None,
|
||||
@@ -1241,6 +1245,7 @@ class RunsClient:
|
||||
assistant_id: The assistant ID or graph name to stream from.
|
||||
If using graph name, will default to first assistant created from that graph.
|
||||
input: The input to the graph.
|
||||
command: A command to execute. Cannot be combined with input.
|
||||
stream_mode: The stream mode(s) to use.
|
||||
stream_subgraphs: Whether to stream output from subgraphs.
|
||||
metadata: Metadata to assign to the run.
|
||||
@@ -1291,6 +1296,7 @@ class RunsClient:
|
||||
""" # noqa: E501
|
||||
payload = {
|
||||
"input": input,
|
||||
"command": command,
|
||||
"config": config,
|
||||
"metadata": metadata,
|
||||
"stream_mode": stream_mode,
|
||||
@@ -1324,6 +1330,7 @@ class RunsClient:
|
||||
assistant_id: str,
|
||||
*,
|
||||
input: Optional[dict] = None,
|
||||
command: Optional[Command] = None,
|
||||
stream_mode: Union[StreamMode, Sequence[StreamMode]] = "values",
|
||||
stream_subgraphs: bool = False,
|
||||
metadata: Optional[dict] = None,
|
||||
@@ -1343,6 +1350,7 @@ class RunsClient:
|
||||
assistant_id: str,
|
||||
*,
|
||||
input: Optional[dict] = None,
|
||||
command: Optional[Command] = None,
|
||||
stream_mode: Union[StreamMode, Sequence[StreamMode]] = "values",
|
||||
stream_subgraphs: bool = False,
|
||||
metadata: Optional[dict] = None,
|
||||
@@ -1363,6 +1371,7 @@ class RunsClient:
|
||||
assistant_id: str,
|
||||
*,
|
||||
input: Optional[dict] = None,
|
||||
command: Optional[Command] = None,
|
||||
stream_mode: Union[StreamMode, Sequence[StreamMode]] = "values",
|
||||
stream_subgraphs: bool = False,
|
||||
metadata: Optional[dict] = None,
|
||||
@@ -1385,6 +1394,7 @@ class RunsClient:
|
||||
assistant_id: The assistant ID or graph name to stream from.
|
||||
If using graph name, will default to first assistant created from that graph.
|
||||
input: The input to the graph.
|
||||
command: A command to execute. Cannot be combined with input.
|
||||
stream_mode: The stream mode(s) to use.
|
||||
stream_subgraphs: Whether to stream output from subgraphs.
|
||||
metadata: Metadata to assign to the run.
|
||||
@@ -1471,6 +1481,7 @@ class RunsClient:
|
||||
""" # noqa: E501
|
||||
payload = {
|
||||
"input": input,
|
||||
"command": command,
|
||||
"stream_mode": stream_mode,
|
||||
"stream_subgraphs": stream_subgraphs,
|
||||
"config": config,
|
||||
@@ -1508,6 +1519,7 @@ class RunsClient:
|
||||
assistant_id: str,
|
||||
*,
|
||||
input: Optional[dict] = None,
|
||||
command: Optional[Command] = None,
|
||||
metadata: Optional[dict] = None,
|
||||
config: Optional[Config] = None,
|
||||
checkpoint: Optional[Checkpoint] = None,
|
||||
@@ -1529,6 +1541,7 @@ class RunsClient:
|
||||
assistant_id: str,
|
||||
*,
|
||||
input: Optional[dict] = None,
|
||||
command: Optional[Command] = None,
|
||||
metadata: Optional[dict] = None,
|
||||
config: Optional[Config] = None,
|
||||
interrupt_before: Optional[Union[All, Sequence[str]]] = None,
|
||||
@@ -1547,6 +1560,7 @@ class RunsClient:
|
||||
assistant_id: str,
|
||||
*,
|
||||
input: Optional[dict] = None,
|
||||
command: Optional[Command] = None,
|
||||
metadata: Optional[dict] = None,
|
||||
config: Optional[Config] = None,
|
||||
checkpoint: Optional[Checkpoint] = None,
|
||||
@@ -1569,6 +1583,7 @@ class RunsClient:
|
||||
assistant_id: The assistant ID or graph name to run.
|
||||
If using graph name, will default to first assistant created from that graph.
|
||||
input: The input to the graph.
|
||||
command: A command to execute. Cannot be combined with input.
|
||||
metadata: Metadata to assign to the run.
|
||||
config: The configuration for the assistant.
|
||||
checkpoint: The checkpoint to resume from.
|
||||
@@ -1635,6 +1650,7 @@ class RunsClient:
|
||||
""" # noqa: E501
|
||||
payload = {
|
||||
"input": input,
|
||||
"command": command,
|
||||
"config": config,
|
||||
"metadata": metadata,
|
||||
"assistant_id": assistant_id,
|
||||
|
||||
@@ -339,3 +339,14 @@ class StreamPart(NamedTuple):
|
||||
"""The type of event for this stream part."""
|
||||
data: dict
|
||||
"""The data payload associated with the event."""
|
||||
|
||||
|
||||
class Send(TypedDict):
|
||||
node: str
|
||||
input: Optional[dict[str, Any]]
|
||||
|
||||
|
||||
class Command(TypedDict, total=False):
|
||||
send: Union[Send, Sequence[Send]]
|
||||
update: dict[str, Any]
|
||||
resume: Any
|
||||
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "langgraph-sdk"
|
||||
version = "0.1.35"
|
||||
version = "0.1.36"
|
||||
description = "SDK for interacting with LangGraph API"
|
||||
authors = []
|
||||
license = "MIT"
|
||||
|
||||
Generated
+156
-11
@@ -1,4 +1,4 @@
|
||||
# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand.
|
||||
# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand.
|
||||
|
||||
[[package]]
|
||||
name = "aiohappyeyeballs"
|
||||
@@ -390,6 +390,59 @@ docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphi
|
||||
tests = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"]
|
||||
tests-mypy = ["mypy (>=1.11.1)", "pytest-mypy-plugins"]
|
||||
|
||||
[[package]]
|
||||
name = "autogen"
|
||||
version = "0.3.2"
|
||||
description = "A programming framework for agentic AI"
|
||||
optional = false
|
||||
python-versions = "<3.13,>=3.8"
|
||||
files = [
|
||||
{file = "autogen-0.3.2-py3-none-any.whl", hash = "sha256:e37a9df0ad84cde3429ec63298b8e9eb4e6306a28eec2627171e14b9a61ea64d"},
|
||||
{file = "autogen-0.3.2.tar.gz", hash = "sha256:9f8a1170ac2e5a1fc9efc3cfa6e23261dd014db97b17c8c416f97ee14951bc7b"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
diskcache = "*"
|
||||
docker = "*"
|
||||
flaml = "*"
|
||||
numpy = ">=1.17.0,<2"
|
||||
openai = ">=1.3"
|
||||
packaging = "*"
|
||||
pydantic = ">=1.10,<2.6.0 || >2.6.0,<3"
|
||||
python-dotenv = "*"
|
||||
termcolor = "*"
|
||||
tiktoken = "*"
|
||||
|
||||
[package.extras]
|
||||
anthropic = ["anthropic (>=0.23.1)"]
|
||||
autobuild = ["chromadb", "huggingface-hub", "pysqlite3", "sentence-transformers"]
|
||||
bedrock = ["boto3 (>=1.34.149)"]
|
||||
blendsearch = ["flaml[blendsearch]"]
|
||||
cerebras = ["cerebras-cloud-sdk (>=1.0.0)"]
|
||||
cohere = ["cohere (>=5.5.8)"]
|
||||
cosmosdb = ["azure-cosmos (>=4.2.0)"]
|
||||
gemini = ["google-auth", "google-cloud-aiplatform", "google-generativeai (>=0.5,<1)", "pillow", "pydantic"]
|
||||
graph = ["matplotlib", "networkx"]
|
||||
graph-rag-falkor-db = ["graphrag-sdk"]
|
||||
groq = ["groq (>=0.9.0)"]
|
||||
jupyter-executor = ["ipykernel (>=6.29.0)", "jupyter-client (>=8.6.0)", "jupyter-kernel-gateway", "requests", "websocket-client"]
|
||||
lmm = ["pillow", "replicate"]
|
||||
long-context = ["llmlingua (<0.3)"]
|
||||
mathchat = ["pydantic (==1.10.9)", "sympy", "wolframalpha"]
|
||||
mistral = ["mistralai (>=1.0.1)"]
|
||||
ollama = ["fix-busted-json (>=0.0.18)", "ollama (>=0.3.3)"]
|
||||
redis = ["redis"]
|
||||
retrievechat = ["beautifulsoup4", "chromadb (==0.5.3)", "ipython", "markdownify", "protobuf (==4.25.3)", "pypdf", "sentence-transformers"]
|
||||
retrievechat-mongodb = ["beautifulsoup4", "chromadb (==0.5.3)", "ipython", "markdownify", "protobuf (==4.25.3)", "pymongo (>=4.0.0)", "pypdf", "sentence-transformers"]
|
||||
retrievechat-pgvector = ["beautifulsoup4", "chromadb (==0.5.3)", "ipython", "markdownify", "pgvector (>=0.2.5)", "protobuf (==4.25.3)", "psycopg (>=3.1.18)", "pypdf", "sentence-transformers"]
|
||||
retrievechat-qdrant = ["beautifulsoup4", "chromadb (==0.5.3)", "fastembed (>=0.3.1)", "ipython", "markdownify", "protobuf (==4.25.3)", "pypdf", "qdrant-client", "sentence-transformers"]
|
||||
teachable = ["chromadb"]
|
||||
test = ["ipykernel", "nbconvert", "nbformat", "pandas", "pre-commit", "pytest (>=6.1.1,<8)", "pytest-asyncio", "pytest-cov (>=5)"]
|
||||
together = ["together (>=1.2)"]
|
||||
types = ["ipykernel (>=6.29.0)", "jupyter-client (>=8.6.0)", "jupyter-kernel-gateway", "mypy (==1.9.0)", "pytest (>=6.1.1,<8)", "requests", "websocket-client"]
|
||||
websockets = ["websockets (>=12.0,<13)"]
|
||||
websurfer = ["beautifulsoup4", "markdownify", "pathvalidate", "pdfminer.six"]
|
||||
|
||||
[[package]]
|
||||
name = "babel"
|
||||
version = "2.16.0"
|
||||
@@ -1135,6 +1188,17 @@ wrapt = ">=1.10,<2"
|
||||
[package.extras]
|
||||
dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "sphinx (<2)", "tox"]
|
||||
|
||||
[[package]]
|
||||
name = "diskcache"
|
||||
version = "5.6.3"
|
||||
description = "Disk Cache -- Disk and file backed persistent cache."
|
||||
optional = false
|
||||
python-versions = ">=3"
|
||||
files = [
|
||||
{file = "diskcache-5.6.3-py3-none-any.whl", hash = "sha256:5e31b2d5fbad117cc363ebaf6b689474db18a1f6438bc82358b024abd4c2ca19"},
|
||||
{file = "diskcache-5.6.3.tar.gz", hash = "sha256:2c3a3fa2743d8535d832ec61c2054a1641f41775aa7c556758a109941e33e4fc"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "distro"
|
||||
version = "1.9.0"
|
||||
@@ -1166,6 +1230,28 @@ idna = ["idna (>=3.6)"]
|
||||
trio = ["trio (>=0.23)"]
|
||||
wmi = ["wmi (>=1.5.1)"]
|
||||
|
||||
[[package]]
|
||||
name = "docker"
|
||||
version = "7.1.0"
|
||||
description = "A Python library for the Docker Engine API."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "docker-7.1.0-py3-none-any.whl", hash = "sha256:c96b93b7f0a746f9e77d325bcfb87422a3d8bd4f03136ae8a85b37f1898d5fc0"},
|
||||
{file = "docker-7.1.0.tar.gz", hash = "sha256:ad8c70e6e3f8926cb8a92619b832b4ea5299e2831c14284663184e200546fa6c"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
pywin32 = {version = ">=304", markers = "sys_platform == \"win32\""}
|
||||
requests = ">=2.26.0"
|
||||
urllib3 = ">=1.26.0"
|
||||
|
||||
[package.extras]
|
||||
dev = ["coverage (==7.2.7)", "pytest (==7.4.2)", "pytest-cov (==4.1.0)", "pytest-timeout (==2.1.0)", "ruff (==0.1.8)"]
|
||||
docs = ["myst-parser (==0.18.0)", "sphinx (==5.1.1)"]
|
||||
ssh = ["paramiko (>=2.4.3)"]
|
||||
websockets = ["websocket-client (>=1.3.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "durationpy"
|
||||
version = "0.7"
|
||||
@@ -1271,6 +1357,43 @@ httpx-sse = "*"
|
||||
Pillow = "*"
|
||||
pydantic = "*"
|
||||
|
||||
[[package]]
|
||||
name = "flaml"
|
||||
version = "2.3.2"
|
||||
description = "A fast library for automated machine learning and tuning"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "FLAML-2.3.2-py3-none-any.whl", hash = "sha256:1ee6e8e76bf1d741b4da41e2a2a8c0638b36d90b0f60aac323b5568f54dcb9e7"},
|
||||
{file = "flaml-2.3.2.tar.gz", hash = "sha256:4a1ec289ddaec36850cfc66f6fb335b8521df49ea31f6adb54ea63a5cebb6865"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
NumPy = ">=1.17"
|
||||
|
||||
[package.extras]
|
||||
autogen = ["diskcache", "openai (==0.27.8)", "termcolor"]
|
||||
automl = ["lightgbm (>=2.3.1)", "pandas (>=1.1.4)", "scikit-learn (>=1.0.0)", "scipy (>=1.4.1)", "xgboost (>=0.90,<3.0.0)"]
|
||||
autozero = ["packaging", "pandas", "scikit-learn"]
|
||||
azureml = ["azureml-mlflow"]
|
||||
benchmark = ["catboost (>=0.26)", "pandas (==1.1.4)", "psutil (==5.8.0)", "xgboost (==1.3.3)"]
|
||||
blendsearch = ["optuna (>=2.8.0,<=3.6.1)", "packaging"]
|
||||
catboost = ["catboost (>=0.26,<1.2)", "catboost (>=0.26,<=1.2.5)"]
|
||||
forecast = ["hcrystalball (==0.1.10)", "holidays (<0.14)", "prophet (>=1.0.1)", "pytorch-forecasting (>=0.9.0)", "pytorch-lightning (==1.9.0)", "statsmodels (>=0.12.2)", "tensorboardX (==2.6)"]
|
||||
hf = ["datasets", "nltk (<=3.8.1)", "rouge-score", "seqeval", "transformers[torch] (==4.26)"]
|
||||
mathchat = ["diskcache", "openai (==0.27.8)", "pydantic (==1.10.9)", "sympy", "termcolor", "wolframalpha"]
|
||||
nlp = ["datasets", "nltk (<=3.8.1)", "rouge-score", "seqeval", "transformers[torch] (==4.26)"]
|
||||
nni = ["nni"]
|
||||
notebook = ["jupyter"]
|
||||
openai = ["diskcache", "openai (==0.27.8)"]
|
||||
ray = ["ray[tune] (>=1.13,<2.0)"]
|
||||
retrievechat = ["chromadb", "diskcache", "openai (==0.27.8)", "sentence-transformers", "termcolor", "tiktoken"]
|
||||
spark = ["joblib (<=1.3.2)", "joblibspark (>=0.5.0)", "pyspark (>=3.2.0)"]
|
||||
synapse = ["joblibspark (>=0.5.0)", "optuna (>=2.8.0,<=3.6.1)", "pyspark (>=3.2.0)"]
|
||||
test = ["catboost (>=0.26)", "catboost (>=0.26,<1.2)", "coverage (>=5.3)", "dataclasses", "datasets", "dill", "hcrystalball (==0.1.10)", "ipykernel", "joblib (<=1.3.2)", "joblibspark (>=0.5.0)", "jupyter", "lightgbm (>=2.3.1)", "mlflow (==2.15.1)", "nbconvert", "nbformat", "nltk (<=3.8.1)", "openml", "optuna (>=2.8.0,<=3.6.1)", "packaging", "pandas (>=1.1.4)", "pandas (>=1.1.4,<2.0.0)", "pre-commit", "psutil (==5.8.0)", "pydantic (==1.10.9)", "pytest (>=6.1.1)", "pytorch-forecasting (>=0.9.0,<=0.10.1)", "pytorch-lightning (<1.9.1)", "requests (<2.29.0)", "rgf-python", "rouge-score", "scikit-learn (>=1.0.0)", "scipy (>=1.4.1)", "seqeval", "statsmodels (>=0.12.2)", "sympy", "tensorboardX (==2.6)", "thop", "torch", "torchvision", "transformers[torch] (==4.26)", "wolframalpha", "xgboost (>=0.90,<2.0.0)"]
|
||||
ts-forecast = ["hcrystalball (==0.1.10)", "holidays (<0.14)", "prophet (>=1.0.1)", "statsmodels (>=0.12.2)"]
|
||||
vw = ["scikit-learn", "vowpalwabbit (>=8.10.0,<9.0.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "flatbuffers"
|
||||
version = "24.3.25"
|
||||
@@ -2912,7 +3035,7 @@ langchain-core = ">=0.3.0,<0.4.0"
|
||||
|
||||
[[package]]
|
||||
name = "langgraph"
|
||||
version = "0.2.34"
|
||||
version = "0.2.52"
|
||||
description = "Building stateful, multi-actor applications with LLMs"
|
||||
optional = false
|
||||
python-versions = ">=3.9.0,<4.0"
|
||||
@@ -2920,8 +3043,9 @@ files = []
|
||||
develop = true
|
||||
|
||||
[package.dependencies]
|
||||
langchain-core = ">=0.2.39,<0.4"
|
||||
langgraph-checkpoint = "^2.0.0"
|
||||
langchain-core = ">=0.2.43,<0.4.0,!=0.3.0,!=0.3.1,!=0.3.2,!=0.3.3,!=0.3.4,!=0.3.5,!=0.3.6,!=0.3.7,!=0.3.8,!=0.3.9,!=0.3.10,!=0.3.11,!=0.3.12,!=0.3.13,!=0.3.14"
|
||||
langgraph-checkpoint = "^2.0.4"
|
||||
langgraph-sdk = "^0.1.32"
|
||||
|
||||
[package.source]
|
||||
type = "directory"
|
||||
@@ -2929,7 +3053,7 @@ url = "libs/langgraph"
|
||||
|
||||
[[package]]
|
||||
name = "langgraph-checkpoint"
|
||||
version = "2.0.1"
|
||||
version = "2.0.5"
|
||||
description = "Library with base interfaces for LangGraph checkpoint savers."
|
||||
optional = false
|
||||
python-versions = "^3.9.0,<4.0"
|
||||
@@ -2946,7 +3070,7 @@ url = "libs/checkpoint"
|
||||
|
||||
[[package]]
|
||||
name = "langgraph-checkpoint-postgres"
|
||||
version = "2.0.1"
|
||||
version = "2.0.3"
|
||||
description = "Library with a Postgres implementation of LangGraph checkpoint saver."
|
||||
optional = false
|
||||
python-versions = "^3.9.0,<4.0"
|
||||
@@ -2954,7 +3078,7 @@ files = []
|
||||
develop = true
|
||||
|
||||
[package.dependencies]
|
||||
langgraph-checkpoint = "^2.0.0"
|
||||
langgraph-checkpoint = "^2.0.2"
|
||||
orjson = ">=3.10.1"
|
||||
psycopg = "^3.0.0"
|
||||
psycopg-pool = "^3.0.0"
|
||||
@@ -2965,7 +3089,7 @@ url = "libs/checkpoint-postgres"
|
||||
|
||||
[[package]]
|
||||
name = "langgraph-checkpoint-sqlite"
|
||||
version = "2.0.0"
|
||||
version = "2.0.1"
|
||||
description = "Library with a SQLite implementation of LangGraph checkpoint saver."
|
||||
optional = false
|
||||
python-versions = "^3.9.0"
|
||||
@@ -2974,7 +3098,7 @@ develop = true
|
||||
|
||||
[package.dependencies]
|
||||
aiosqlite = "^0.20.0"
|
||||
langgraph-checkpoint = "^2.0.0"
|
||||
langgraph-checkpoint = "^2.0.2"
|
||||
|
||||
[package.source]
|
||||
type = "directory"
|
||||
@@ -2982,7 +3106,7 @@ url = "libs/checkpoint-sqlite"
|
||||
|
||||
[[package]]
|
||||
name = "langgraph-sdk"
|
||||
version = "0.1.32"
|
||||
version = "0.1.36"
|
||||
description = "SDK for interacting with LangGraph API"
|
||||
optional = false
|
||||
python-versions = "^3.9.0,<4.0"
|
||||
@@ -4974,6 +5098,7 @@ description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "pyasn1-0.6.1-py3-none-any.whl", hash = "sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629"},
|
||||
{file = "pyasn1-0.6.1.tar.gz", hash = "sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034"},
|
||||
]
|
||||
|
||||
@@ -4984,6 +5109,7 @@ description = "A collection of ASN.1-based protocols modules"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "pyasn1_modules-0.4.1-py3-none-any.whl", hash = "sha256:49bfa96b45a292b711e986f222502c1c9a5e1f4e568fc30e2574a6c7d07838fd"},
|
||||
{file = "pyasn1_modules-0.4.1.tar.gz", hash = "sha256:c28e2dbf9c06ad61c71a075c7e0f9fd0f1b0bb2d2ad4377f240d33ac2ab60a7c"},
|
||||
]
|
||||
|
||||
@@ -6043,6 +6169,11 @@ files = [
|
||||
{file = "scikit_learn-1.5.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f60021ec1574e56632be2a36b946f8143bf4e5e6af4a06d85281adc22938e0dd"},
|
||||
{file = "scikit_learn-1.5.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:394397841449853c2290a32050382edaec3da89e35b3e03d6cc966aebc6a8ae6"},
|
||||
{file = "scikit_learn-1.5.2-cp312-cp312-win_amd64.whl", hash = "sha256:57cc1786cfd6bd118220a92ede80270132aa353647684efa385a74244a41e3b1"},
|
||||
{file = "scikit_learn-1.5.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e9a702e2de732bbb20d3bad29ebd77fc05a6b427dc49964300340e4c9328b3f5"},
|
||||
{file = "scikit_learn-1.5.2-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:b0768ad641981f5d3a198430a1d31c3e044ed2e8a6f22166b4d546a5116d7908"},
|
||||
{file = "scikit_learn-1.5.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:178ddd0a5cb0044464fc1bfc4cca5b1833bfc7bb022d70b05db8530da4bb3dd3"},
|
||||
{file = "scikit_learn-1.5.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f7284ade780084d94505632241bf78c44ab3b6f1e8ccab3d2af58e0e950f9c12"},
|
||||
{file = "scikit_learn-1.5.2-cp313-cp313-win_amd64.whl", hash = "sha256:b7b0f9a0b1040830d38c39b91b3a44e1b643f4b36e36567b80b7c6bd2202a27f"},
|
||||
{file = "scikit_learn-1.5.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:757c7d514ddb00ae249832fe87100d9c73c6ea91423802872d9e74970a0e40b9"},
|
||||
{file = "scikit_learn-1.5.2-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:52788f48b5d8bca5c0736c175fa6bdaab2ef00a8f536cda698db61bd89c551c1"},
|
||||
{file = "scikit_learn-1.5.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:643964678f4b5fbdc95cbf8aec638acc7aa70f5f79ee2cdad1eec3df4ba6ead8"},
|
||||
@@ -6362,6 +6493,20 @@ files = [
|
||||
doc = ["reno", "sphinx"]
|
||||
test = ["pytest", "tornado (>=4.5)", "typeguard"]
|
||||
|
||||
[[package]]
|
||||
name = "termcolor"
|
||||
version = "2.5.0"
|
||||
description = "ANSI color formatting for output in terminal"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
files = [
|
||||
{file = "termcolor-2.5.0-py3-none-any.whl", hash = "sha256:37b17b5fc1e604945c2642c872a3764b5d547a48009871aea3edd3afa180afb8"},
|
||||
{file = "termcolor-2.5.0.tar.gz", hash = "sha256:998d8d27da6d48442e8e1f016119076b690d962507531df4890fcd2db2ef8a6f"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
tests = ["pytest", "pytest-cov"]
|
||||
|
||||
[[package]]
|
||||
name = "terminado"
|
||||
version = "0.18.1"
|
||||
@@ -7331,4 +7476,4 @@ type = ["pytest-mypy"]
|
||||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = "^3.10"
|
||||
content-hash = "738e69cf406b140217cc8c3c0f2ccb5c2027d4701bcf2a347ba4b107f700ab2a"
|
||||
content-hash = "776ee42630769f08e3896338f18ec81830166695d32d2208dc31dedb22d3b22d"
|
||||
@@ -54,6 +54,7 @@ motor = "^3.5.1"
|
||||
grandalf = "^0.8"
|
||||
pyppeteer = "^2.0.0"
|
||||
networkx = "^3.3"
|
||||
autogen = { version = "^0.3.0", python = "<3.13,>=3.8" }
|
||||
|
||||
[tool.poetry.group.test]
|
||||
optional = true
|
||||
|
||||
Reference in new issue
Block a user