diff --git a/docs/_scripts/copy_notebooks.py b/docs/_scripts/copy_notebooks.py index 0aae600bf..ff156f0e7 100644 --- a/docs/_scripts/copy_notebooks.py +++ b/docs/_scripts/copy_notebooks.py @@ -8,6 +8,8 @@ examples_dir = root_dir / "examples" docs_dir = root_dir / "docs/docs" how_tos_dir = docs_dir / "how-tos" tutorials_dir = docs_dir / "tutorials" +cloud_how_tos_dir = docs_dir / "deploy/how-tos" +cloud_sdk_dir = docs_dir / "deploy" _MANUAL = { "how-tos": [ @@ -96,6 +98,10 @@ def copy_notebooks(): continue if any(path in _HOW_TOS for path in root.split(os.sep)): dst_dir = how_tos_dir + elif 'sdk' in root.split(os.sep): + dst_dir = cloud_sdk_dir + elif 'cloud_examples' in root.split(os.sep): + dst_dir = cloud_how_tos_dir else: dst_dir = tutorials_dir for file in files: @@ -118,7 +124,6 @@ def copy_notebooks(): ) print(f"Overriding: {src_path} to {dst_path}") break - # Avoid double nesting. dst_path = dst_path.replace("tutorials/tutorials", "tutorials").replace( "how-tos/how-tos", "how-tos" @@ -135,6 +140,7 @@ def copy_notebooks(): with open(dst_path, "w") as f: f.write(content) dst_dir = dst_dir_ + # Top level notebooks are "how-to's" # for file in examples_dir.iterdir(): # if file.suffix.endswith(".ipynb") and not os.path.isdir( @@ -144,7 +150,6 @@ def copy_notebooks(): # dst_path = os.path.join(docs_dir, "how-tos", file.name) # shutil.copy(src_path, dst_path) - if __name__ == "__main__": clean_notebooks() copy_notebooks() diff --git a/docs/docs/deploy/api_concepts.md b/docs/docs/deploy/api_concepts.md new file mode 100644 index 000000000..030251396 --- /dev/null +++ b/docs/docs/deploy/api_concepts.md @@ -0,0 +1,24 @@ +# API Concepts +This page discusses high-level concepts of the LangGraph Cloud. + +## Assistant +An assistant is a configured instance of a [`CompiledGraph`](../../reference/graphs/#compiledgraph). It abstracts the cognitive architecture of the graph and contains instance specific configuration and metadata. Multiple assistants can reference the same graph but can contain different configuration and metadata, which may differentiate the behavior of the assistants. + +An assistant (i.e. the graph) is invoked as part of a [run](#run). + +## Thread +A thread contains the accumulated state of a group of [runs](#run). If a run is executed on a thread, then the [state](../../concepts/#state-management) of the underlying graph of the [assistant](#assistant) will be persisted to the thread. A thread's current and historical state can be retrieved. + +To persist state, a thread must be created prior to executing a run. + +## Run +A run is an invocation of an [assistant](#assistant). Each run may have its own input, configuration, and metadata, which may affect execution and output of the underlying graph. A run can optionally be executed on a [thread](#thread). + +## Streaming +Streaming is critical in making applications based on LLMs feel responsive to end-users. There are three different ways to stream with graphs: by [values](../how_tos/cloud_examples/stream_values/), by [messages](../how_tos/cloud_examples/stream_messages/), and by [updates](../how_tos/cloud_examples/stream_updates/). + +## Human-in-the-Loop +There are many occasions where the graph cannot run completely autonomously. For instance, the user might need to input some additional arguments to a function call, or select the next edge for the graph to continue on. In these instances, we need to insert some human in the loop interaction, which you can learn about in [this how-to](../how_tos/cloud_examples/human-in-the-loop_cloud). + +## Multi-Tasking +Many times users might interact with your graph in unintended ways. For instance, a user interacting with a graph that has chat output could send one message and before the graph has finished running send a second message. To solve this issue of "double-texting" (i.e. prompting the graph a second time before the first run has finished), Langgraph has provided four different solutions, all of which are covered in the [Double Texting how-tos](../how_tos/cloud_examples/interrupt_concurrent/). diff --git a/docs/docs/deploy/deployment/img/api_page.png b/docs/docs/deploy/deployment/img/api_page.png new file mode 100644 index 000000000..589f565d2 Binary files /dev/null and b/docs/docs/deploy/deployment/img/api_page.png differ diff --git a/docs/docs/deploy/deployment/img/deploy_filled_out.png b/docs/docs/deploy/deployment/img/deploy_filled_out.png new file mode 100644 index 000000000..e43b9e19f Binary files /dev/null and b/docs/docs/deploy/deployment/img/deploy_filled_out.png differ diff --git a/docs/docs/deploy/deployment/img/deployed_page.png b/docs/docs/deploy/deployment/img/deployed_page.png new file mode 100644 index 000000000..ccae96a1d Binary files /dev/null and b/docs/docs/deploy/deployment/img/deployed_page.png differ diff --git a/docs/docs/deploy/deployment/img/deployment_page.png b/docs/docs/deploy/deployment/img/deployment_page.png new file mode 100644 index 000000000..606436ac8 Binary files /dev/null and b/docs/docs/deploy/deployment/img/deployment_page.png differ diff --git a/docs/docs/deploy/deployment/img/graph_visualization.png b/docs/docs/deploy/deployment/img/graph_visualization.png new file mode 100644 index 000000000..6a4190bec Binary files /dev/null and b/docs/docs/deploy/deployment/img/graph_visualization.png differ diff --git a/docs/docs/deploy/deployment/managed.md b/docs/docs/deploy/deployment/managed.md new file mode 100644 index 000000000..4134d0fb9 --- /dev/null +++ b/docs/docs/deploy/deployment/managed.md @@ -0,0 +1,157 @@ +# Deploy custom LangGraph code with LangGraph Cloud (Python) + +## Set up your application code + +### Create a new application + +To create a new app called create a directory with the following structure + +``` +/ +|-- agent.py # code for your LangGraph agent +|-- requirements.txt # python packages required for your graph +|-- langgraph.json # configuration file for langgraph +|-- .env # environment files with API keys +``` + +### Agent File + +In your agent file, you can define as many graphs (agents) as you would like. For our example we are going to create the simplest graph possible: a one node graph. You can read about adding more complexity to your graphs in the [docs](https://langchain-ai.github.io/langgraph/tutorials/introduction/). + +Here is what our `agent.py` file looks like for this example: + +```python +from langchain_openai import ChatOpenAI +from langgraph.graph import END, MessageGraph + +model = ChatOpenAI(temperature=0) + +graph_workflow = MessageGraph() + +graph_workflow.add_node("oracle", model) +graph_workflow.add_edge("oracle", END) + +graph_workflow.set_entry_point("oracle") +graph = graph_workflow.compile() +``` + +### Configuration file + +- `langgraph.json` is a configuration file with three parts: + - `graphs` + - Pass in the graphs you want to host on your deployment, using the graph_id as the key and the path to the agent (a CompiledGraph) as the value. In our example we only use one graph, so the json looks like so: + + ```json + "graphs": { + "agent": "./agent.py:graph" + }, + ``` + + - `dependencies` + - Pass in a list of the dependencies you would like to be installed in order to host your app. In our case, we don’t need any additional dependencies besides our `requirements.txt` file, but if we did we could append them to the dependencies list using the names of the additional packages we want installed. + + ```json + "dependencies": ["."], + ``` + + - `env` + - This is simply a path to our environment file containing all variables/files to load. + + ```json + "env": ".env" + ``` + + +Putting it all together, our `langgraph.json` file should look like this: + +```json +{ + "dependencies": ["."], + "graphs": { + "agent": "./agent.py:agent" + }, + "env": ".env" +} +``` + +### Environment and Package Requirement files + +- The `.env` contains any environment variables that are needed to run your code. In our example the file looks like follows: + +```python +OPENAI_API_KEY= ... +LANGCHAIN_API_KEY= ... +LANGCHAIN_TRACING_V2=true +``` + +- The `requirements.txt` file lists python package dependencies for your project (along with the associated versions if necessary). In our example the file looks like this: + + ``` + langgraph + langchain_openai + ``` + +### Push your code to GitHub + +Create a git repo in the `` directory, and verify it’s existence. You can use the GitHub CLI if you like, or just create a repo manually. + +## Host your code on LangGraph Cloud + +### Deploy from GitHub with LangGraph Cloud + +Head to LangSmith and click on the 🚀 icon on the left navbar to create a new deployment. Click the `+ New Deployment` button. + +***If you have not deployed to LangGraph Cloud before:*** there will be a button that shows up saying Import from GitHub. You’ll need to follow that flow to connect LangGraph Cloud to GitHub. + +***Once you have set up your GitHub connection:*** the new deployment page will look as follows + +![Screenshot 2024-06-11 at 1.17.03 PM.png](./img/deployment_page.png) + +To deploy your application, you should do the following: + +1. Select your GitHub username or organization from the selector +2. Search for your repo to deploy in the search bar and select it +3. Choose any name +4. In the `LangGraph API config file` field, enter the path to your `langgraph.json` file (if left blank langsmith will automatically search for it on deployment) +5. For Git Reference, you can select either the git branch for the code you want to deploy, or the exact commit SHA. +6. If your chain relies on environment variables (for example, an OPENAI_API_KEY), add those in. They will be propagated to the underlying server so your code can access them. + +Putting this all together, you should have something as follows for your deployment details: + +![Screenshot 2024-06-11 at 1.21.52 PM.png](./img/deploy_filled_out.png) + +Hit `Submit` and your application will start deploying! + +## Inspect Traces + Monitor Service + +### Deployments View + +After your deployment is complete, your deployments page should look as follows: + +![Screenshot 2024-06-11 at 2.03.34 PM.png](./img/deployed_page.png) + +You can see that by default, you get access to the `Trace Count` monitoring chart and `Recent Traces` run view. These are powered by LangSmith. + +You can click on `All Charts` to view all monitoring info for your server, or click on `See tracing project` to get more information on an individual trace. + +### Access the Docs + +You can access the docs by clicking on the API DOCS link, which should send you to a page that looks like this: + +![Screenshot 2024-06-19 at 2.27.24 PM.png](./img/api_page.png) + +You won’t actually be able to test any of the API endpoints without authorizing first. To do so, click on the Authorize button in the top right corner, input your `LANGCHAIN_API_KEY` in the `API Key` box, and then click `Authorize` to finish the process. You should now be able to select any of the API endpoints, click `Try it out` , enter the parameters you would like to pass, and then click `Execute` to view the results of the API call. + +## Interact with your deployment via LangGraph Studio + +### Access Studio + +If you click on your deployment you should see a blue button in the top right that says `LangGraph Studio`. Clicking on this button will take you to a page that looks like this: + +![Screenshot 2024-06-11 at 2.51.51 PM.png](./img/graph_visualiztion) + +On this page you can test out your graph by passing in starting states and clicking `Start Run` (this should behave identically to calling `.invoke`). You will then be able to look into the execution thread for each run and explore the steps your graph is taking to produce its output. + +## Deploy new code + +To deploy new code that you push to GitHub, simply navigate to the deployments page, and hit `+ New Revision`. LangGraph Cloud releases what it calls a new “revision” every time you deploy code. Therefore, your first deployment automatically showed revisions. A Revision always corresponds to a new piece of code being deployed. A modal will pop up to enter new revision info. This can be thought of as a partial update on the last revision, so you do not need to enter any fields that didn’t change (*note: environment variables are not saved between revisions, you must re-enter them for each new revision)*. \ No newline at end of file diff --git a/docs/docs/deploy/deployment/self_hosted.md b/docs/docs/deploy/deployment/self_hosted.md new file mode 100644 index 000000000..4a9fe33d9 --- /dev/null +++ b/docs/docs/deploy/deployment/self_hosted.md @@ -0,0 +1,9 @@ +### Run your server locally + +First, make sure that Docker is up and running. Test that your server works by running: + +```python +langgraph up -c langgraph.json +``` + +This will bring up a local server with your graph! Access the auto-generated server for your playground to confirm everything works as planned at [http://localhost:8124](http://localhost:8124) . \ No newline at end of file diff --git a/docs/docs/deploy/index.md b/docs/docs/deploy/index.md new file mode 100644 index 000000000..6b88bac7d --- /dev/null +++ b/docs/docs/deploy/index.md @@ -0,0 +1,11 @@ +# LangGraph Cloud (alpha) + +!!! danger "Important" + LangGraph Cloud is a closed source, paid product in closed alpha stage. Self-hosting LangGraph Cloud applications is only permitted with explicit approval from LangChain. + +!!! warning "Under Construction" + LangGraph Cloud documentation is under construction. Contents may change until general availability. + +LangGraph Cloud is a managed service for deploying and hosting LangGraph applications. Deploying your application with LangGraph Cloud shortens the time-to-market for developers. With one click, start a production-ready HTTP microservice with built-in persistence for your LangGraph application. + +LangGraph Cloud is seamlessly integrated with [LangSmith](https://www.langchain.com/langsmith) and is accessible from within the LangSmith UI. diff --git a/docs/docs/deploy/quick_start.md b/docs/docs/deploy/quick_start.md new file mode 100644 index 000000000..e22304361 --- /dev/null +++ b/docs/docs/deploy/quick_start.md @@ -0,0 +1,70 @@ +# Quick Start +This quick start guide will cover how to develop an application for LangGraph Cloud, run it locally in Docker, and call the APIs to invoke a graph. + +Alternatively, clone or fork the [`langgraph/example`](https://github.com/langchain-ai/langgraph-example) GitHub repository and follow the instructions in the `README`. + +## Develop +1. Create a new application with the following directory and files: + + / + |-- agent.py # code for your LangGraph agent + |-- requirements.txt # Python packages required for your graph + |-- langgraph.json # configuration file for LangGraph + |-- .env # environment files with API keys + +2. The `agent.py` file should contain the following Python code for defining a simple graph: + + ```python + from langchain_openai import ChatOpenAI + from langgraph.graph import END, MessageGraph + + model = ChatOpenAI(temperature=0) + + graph_workflow = MessageGraph() + + graph_workflow.add_node("agent", model) + graph_workflow.add_edge("agent", END) + graph_workflow.set_entry_point("agent") + + graph = graph_workflow.compile() + ``` + +3. The `requirements.txt` file should contain the following dependencies: + + langgraph + langchain_openai + +4. The `langgraph.json` file should contain the following JSON object: + + ```json + { + "dependencies": ["."], + "graphs": { + "agent": "./agent.py:graph" + }, + "env": ".env" + } + ``` + + Learn more about the LangGraph CLI configuration file [here](./reference/cli.md#configuration-file). + +5. The `.env` file should contain the environment variables: + + OPENAI_API_KEY= + LANGGRAPH_AUTH_TYPE=noop + + !!! warning "Disable Authentication" + When testing locally, set `LANGGRAPH_AUTH_TYPE` to `noop` to disable authentication. + +## Run Locally +1. Install the [LangGraph CLI](./reference/cli.md#installation). + +2. Run the following command to start the API server in Docker: + + langgraph up -c langgraph.json + +3. The API server is now running at `http://localhost:8123`. Navigate to [`http://localhost:8123/docs`](http://localhost:8123/docs) to view the API docs. + +## Deploy to Cloud + +Follow [these instructions](./deployment/managed.md#deploy-from-github-with-hosted-langgraph) to deploy to LangGraph Cloud. diff --git a/docs/docs/deploy/reference/api_ref.md b/docs/docs/deploy/reference/api_ref.md new file mode 100644 index 000000000..70ad43dce --- /dev/null +++ b/docs/docs/deploy/reference/api_ref.md @@ -0,0 +1,2 @@ +# API Reference +Coming soon diff --git a/docs/docs/deploy/reference/cli.md b/docs/docs/deploy/reference/cli.md new file mode 100644 index 000000000..52c2551d4 --- /dev/null +++ b/docs/docs/deploy/reference/cli.md @@ -0,0 +1,142 @@ +# LangGraph CLI +The LangGraph CLI includes commands to build and run a LangGraph Cloud server locally in [Docker](https://www.docker.com/). For development and testing, use the CLI to deploy a local API server. + +## Installation +1. Ensure that Docker is installed (e.g. `docker --version`). +1. Install the `langgraph-cli` Python package (e.g. `pip install langgraph-cli`). +1. Run the command `langgraph --help` to confirm that the CLI is installed. + +## Configuration File +The LangGraph CLI requires a JSON configuration file with the following keys: + +| Key | Description | +| --- | ----------- | +| `dependencies` | **Required**. Array of dependencies for LangGraph Deploy API server. Dependencies can be one of the following: (1) `"."`, which will look for local Python packages, (2) `pyproject.toml`, `setup.py` or `requirements.txt` in the app directory `"./local_package"`, or (3) a package name. | +| `graphs` | **Required**. Mapping from graph ID to path where the compiled graph is defined. Example: `./your_package/your_file.py:variable`, where `variable` is an instance of `langgraph.graph.graph.CompiledGraph`. | +| `env` | Path to `.env` file or a mapping from environment variable to its value. | +| `python_version` | `3.11` or `3.12`. Defaults to `3.11`. | +| `pip_config_file`| Path to `pip` config file. | +| `dockerfile_lines` | Array of additional lines to add to Dockerfile following the import from parent image. | + +
+

Note

+

+ The LangGraph CLI defaults to using the configuration file langgraph.json in the current directory. +

+
+ +Example: +```json +{ + "dependencies": [ + "langchain_openai", + "./your_package" + ], + "graphs": { + "my_graph_id": "./your_package/your_file.py:variable" + }, + "env": "./.env" +} +``` + +Example: +```json +{ + "python_version": "3.11", + "dependencies": [ + "langchain_openai", + "." + ], + "graphs": { + "my_graph_id": "./your_package/your_file.py:variable" + }, + "env": { + "OPENAI_API_KEY": "secret-key" + } +} +``` + +## Commands +The base command for the LangGraph CLI is `langgraph`. + +**Usage** +``` +langgraph [OPTIONS] COMMAND [ARGS] +``` + +### `build` +Build LangGraph Deploy API server Docker image. + +**Usage** +``` +langgraph build [OPTIONS] +``` + +**Options** + +| Option | Default | Description | +| ------ | ------- | ----------- | +| `--platform TEXT` | | Target platform(s) to build the Docker image for. Example: `langgraph build --platform linux/amd64,linux/arm64` | +| `-t, --tag TEXT` | | **Required**. Tag for the Docker image. Example: `langgraph build -t my-image` | +| `--pull / --no-pull` | `--pull` | Build with latest remote Docker image. Use `--no-pull` for running the LangGraph Deploy API server with locally built images. | +| `-c, --config FILE` | `langgraph.json` | Path to configuration file declaring dependencies, graphs and environment variables. | +| `--help` | | Display command documentation. | + +### `down` +Stop LangGraph Deploy API server. + +**Usage** +``` +langgraph down [OPTIONS] +``` + +**Options** + +| Option | Default | Description | +| ------ | ------- | ----------- | +| `--debugger-port INTEGER` | | Pull the debugger image locally and serve the UI on specified port. | +| `--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` | | Advanced. Path to `docker-compose.yml` file with additional services to launch. | +| `-p, --port INTEGER` | `8123` | Port to expose. Example: `langgraph up --port 8000` | +| `--help` | | Display command documentation. | + +### `logs` +Show LangGraph Deploy API server logs. + +**Usage** +``` +langgraph logs [OPTIONS] +``` + +**Options** + +| Option | Default | Description | +| ------ | ------- | ----------- | +| `-f, --follow` | | Follow logs. | +| `-c, --config FILE` | `langgraph.json` | Path to configuration file declaring dependencies, graphs and environment variables. | +| `-d, --docker-compose FILE` | | Advanced. Path to `docker-compose.yml` file with additional services to launch. | +| `--help` | | Display command documentation. | + +### `up` +Start LangGraph Deploy API server. + +**Usage** +``` +langgraph up [OPTIONS] +``` + +**Options** + +| Option | Default | Description | +| ------ | ------- | ----------- | +| `--wait` | | Wait for services to start before returning. Implies `--detach`. | +| `--watch` | | Restart on file changes. | +| `--debugger-port INTEGER` | | Pull the debugger image locally and serve the UI on specified port. | +| `--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` | | Advanced. Path to `docker-compose.yml` file with additional services to launch. | +| `-p, --port INTEGER` | `8123` | Port to expose. Example: `langgraph up --port 8000` | +| `--pull / --no-pull` | `--pull` | Build with latest remote Docker image. Use `--no-pull` for running the LangGraph Deploy API server with locally built images. | +| `--recreate / --no-recreate` | `--no-recreate` | Recreate containers even if their configuration and image haven't changed. | +| `--help` | | Display command documentation. | diff --git a/docs/docs/deploy/reference/sdk/js_ts_sdk_ref.md b/docs/docs/deploy/reference/sdk/js_ts_sdk_ref.md new file mode 100644 index 000000000..4da25075a --- /dev/null +++ b/docs/docs/deploy/reference/sdk/js_ts_sdk_ref.md @@ -0,0 +1,2 @@ +# JS/TS SDK Reference +Coming soon diff --git a/docs/docs/deploy/reference/sdk/python_sdk_ref.md b/docs/docs/deploy/reference/sdk/python_sdk_ref.md new file mode 100644 index 000000000..83d274d98 --- /dev/null +++ b/docs/docs/deploy/reference/sdk/python_sdk_ref.md @@ -0,0 +1,2 @@ +# Python SDK Reference +Coming soon diff --git a/docs/docs/deploy/sdk/img/graph_diagram.png b/docs/docs/deploy/sdk/img/graph_diagram.png new file mode 100644 index 000000000..2105cde69 Binary files /dev/null and b/docs/docs/deploy/sdk/img/graph_diagram.png differ diff --git a/docs/docs/deploy/sdk/img/thread_diagram.png b/docs/docs/deploy/sdk/img/thread_diagram.png new file mode 100644 index 000000000..ec938ac6f Binary files /dev/null and b/docs/docs/deploy/sdk/img/thread_diagram.png differ diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 7699f0694..423537a48 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -164,8 +164,40 @@ nav: - Graphs: reference/graphs.md - Checkpointing: reference/checkpoints.md - Prebuilt Components: reference/prebuilt.md - - Errors: reference/errors.md - + - Errors: reference/errors.md + - 'Cloud (alpha)': + - 'deploy/index.md' + - Tutorials: + - Quick Start: 'deploy/quick_start.md' + - SDK: + - Python: 'deploy/sdk/python_sdk.ipynb' + - JS/TS: 'deploy/sdk/js_sdk.ipynb' + - Deployment: + - Self-Hosted: 'deploy/deployment/self_hosted.md' + - Managed: 'deploy/deployment/managed.md' + - How-to Guides: + - Streaming: + - Stream Messaages: 'deploy/how-tos/cloud_examples/stream_messages.ipynb' + - Stream Values: 'deploy/how-tos/cloud_examples/stream_values.ipynb' + - Stream Updates: 'deploy/how-tos/cloud_examples/stream_updates.ipynb' + - Double Texting: + - Interrupt: 'deploy/how-tos/cloud_examples/interrupt_concurrent.ipynb' + - Rollback: 'deploy/how-tos/cloud_examples/rollback_concurrent.ipynb' + - Reject: 'deploy/how-tos/cloud_examples/reject_concurrent.ipynb' + - Enqueue: 'deploy/how-tos/cloud_examples/enqueue_concurrent.ipynb' + - Run Agent in Background: 'deploy/how-tos/cloud_examples/background_run.ipynb' + - Run Multiple Agents in Thread: 'deploy/how-tos/cloud_examples/same-thread.ipynb' + - Human in the loop: 'deploy/how-tos/cloud_examples/human-in-the-loop_cloud.ipynb' + - Create Agents with Configuration: 'deploy/how-tos/cloud_examples/configuration_cloud.ipynb' + - Conceptual Guides: + - API Concepts: 'deploy/api_concepts.md' + - Reference: + - API: 'deploy/reference/api_ref.md' + - SDK: + - Python: 'deploy/reference/sdk/python_sdk_ref.md' + - JS/TS: 'deploy/reference/sdk/js_ts_sdk_ref.md' + - CLI: 'deploy/reference/cli.md' + markdown_extensions: - abbr diff --git a/examples/cloud_examples/background_run.ipynb b/examples/cloud_examples/background_run.ipynb new file mode 100644 index 000000000..87efa3a0b --- /dev/null +++ b/examples/cloud_examples/background_run.ipynb @@ -0,0 +1,625 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "51466c8d-8ce4-4b3d-be4e-18fdbeda5f53", + "metadata": {}, + "source": [ + "# How to kick off background runs\n", + "\n", + "This guide covers how to kick off background runs for your agent.\n", + "This can be useful for long running jobs." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b8e6408a-b37e-428f-9567-077fa55d58e8", + "metadata": {}, + "outputs": [], + "source": [ + "# Initialize the client\n", + "from langgraph_sdk import get_client\n", + "\n", + "client = get_client()" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "4947e9bc-111f-4991-8c41-1041da9bf0ba", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[{'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca',\n", + " 'graph_id': 'agent',\n", + " 'config': {},\n", + " 'created_at': '2024-05-18T00:19:39.688822+00:00',\n", + " 'updated_at': '2024-05-18T00:19:39.688822+00:00',\n", + " 'metadata': {'created_by': 'system'}}]" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# List available assistants\n", + "assistants = await client.assistants.search()\n", + "assistants" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "230c0464-a6e5-420f-9e38-ca514e5634ce", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca',\n", + " 'graph_id': 'agent',\n", + " 'config': {},\n", + " 'created_at': '2024-05-18T00:19:39.688822+00:00',\n", + " 'updated_at': '2024-05-18T00:19:39.688822+00:00',\n", + " 'metadata': {'created_by': 'system'}}" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Get the first assistant, we will use this one\n", + "assistant = assistants[0]\n", + "assistant" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "56aa5159-5583-4134-9210-709b969bda6f", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'thread_id': '6ada015b-b47a-4c4f-a5cd-580893cb6d0c',\n", + " 'created_at': '2024-05-18T00:50:26.367620+00:00',\n", + " 'updated_at': '2024-05-18T00:50:26.367620+00:00',\n", + " 'metadata': {}}" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Create a new thread\n", + "thread = await client.threads.create()\n", + "thread" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "147c3f98-f889-4f05-a090-6b31f2a0b291", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[]" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# If we list runs on this thread, we can see it is empty\n", + "runs = await client.runs.list(thread['thread_id'])\n", + "runs" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8c7b44ef-4816-496d-88a1-2f7327cf576d", + "metadata": {}, + "outputs": [], + "source": [ + "# Let's kick off a run\n", + "input = {\"messages\": [{\"role\": \"human\", \"content\": \"whats the weather in sf\"}]}\n", + "run = await client.runs.create(thread['thread_id'], assistant[\"assistant_id\"], input=input)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "d84b4d80-b0aa-4d9f-a05d-0744b2fe8f72", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'run_id': 'e843abcb-e478-421b-91e1-a8ae171b14f4',\n", + " 'thread_id': '6ada015b-b47a-4c4f-a5cd-580893cb6d0c',\n", + " 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca',\n", + " 'created_at': '2024-05-18T00:50:27.618761+00:00',\n", + " 'updated_at': '2024-05-18T00:50:27.618761+00:00',\n", + " 'status': 'pending',\n", + " 'metadata': {}}" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# The first time we poll it, we can see `status=pending`\n", + "await client.runs.get(thread['thread_id'], run['run_id'])" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "ce124bd3-f197-4b73-9ff6-bb36730dd003", + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "data": { + "text/plain": [ + "[{'event_id': '3ac6d963-442f-481c-9fde-b8a27bc0e277',\n", + " 'run_id': 'e843abcb-e478-421b-91e1-a8ae171b14f4',\n", + " 'received_at': '2024-05-18T00:50:29.570649+00:00',\n", + " 'span_id': 'd4e4a6ee-da2f-4b5f-b656-1a1f73065161',\n", + " 'event': 'on_tool_start',\n", + " 'name': 'tavily_search_results_json',\n", + " 'data': {'input': {'query': 'weather in san francisco'}},\n", + " 'metadata': {'graph_id': 'agent',\n", + " 'thread_id': '6ada015b-b47a-4c4f-a5cd-580893cb6d0c',\n", + " 'created_by': 'system',\n", + " 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'},\n", + " 'tags': ['seq:step:1']},\n", + " {'event_id': 'f8961760-6f13-40e6-9eef-6d4d68e0ed19',\n", + " 'run_id': 'e843abcb-e478-421b-91e1-a8ae171b14f4',\n", + " 'received_at': '2024-05-18T00:50:29.569708+00:00',\n", + " 'span_id': '73295bb1-6cd3-403d-abc1-4f9d96a63894',\n", + " 'event': 'on_chain_start',\n", + " 'name': 'action',\n", + " 'data': {},\n", + " 'metadata': {'graph_id': 'agent',\n", + " 'thread_id': '6ada015b-b47a-4c4f-a5cd-580893cb6d0c',\n", + " 'created_by': 'system',\n", + " 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'},\n", + " 'tags': ['graph:step:2']},\n", + " {'event_id': '44b5f815-f60c-468f-8d23-96dfdaa0ed20',\n", + " 'run_id': 'e843abcb-e478-421b-91e1-a8ae171b14f4',\n", + " 'received_at': '2024-05-18T00:50:29.568202+00:00',\n", + " 'span_id': 'e843abcb-e478-421b-91e1-a8ae171b14f4',\n", + " 'event': 'on_chain_stream',\n", + " 'name': 'LangGraph',\n", + " 'data': {'chunk': {'messages': [{'id': '46b31c3a-01bf-4946-bd5a-fa6a7f6c97ce',\n", + " 'name': None,\n", + " 'type': 'human',\n", + " 'content': 'whats the weather in sf',\n", + " 'example': False,\n", + " 'additional_kwargs': {},\n", + " 'response_metadata': {}},\n", + " {'id': 'run-4885d5a0-cd89-4f00-8558-e85b542a710c',\n", + " 'name': None,\n", + " 'type': 'ai',\n", + " 'content': [{'id': 'toolu_018yyEfJHihdVfWypRNLqDug',\n", + " 'name': 'tavily_search_results_json',\n", + " 'type': 'tool_use',\n", + " 'input': {'query': 'weather in san francisco'}}],\n", + " 'example': False,\n", + " 'tool_calls': [{'id': 'toolu_018yyEfJHihdVfWypRNLqDug',\n", + " 'args': {'query': 'weather in san francisco'},\n", + " 'name': 'tavily_search_results_json'}],\n", + " 'additional_kwargs': {},\n", + " 'response_metadata': {},\n", + " 'invalid_tool_calls': []}]}},\n", + " 'metadata': {'graph_id': 'agent',\n", + " 'thread_id': '6ada015b-b47a-4c4f-a5cd-580893cb6d0c',\n", + " 'created_by': 'system',\n", + " 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'},\n", + " 'tags': []},\n", + " {'event_id': '0917b31d-f49d-43d4-a8ed-a9eebd8904e8',\n", + " 'run_id': 'e843abcb-e478-421b-91e1-a8ae171b14f4',\n", + " 'received_at': '2024-05-18T00:50:29.566761+00:00',\n", + " 'span_id': 'f469ac2e-17a3-491c-bc03-0c56aa30a68b',\n", + " 'event': 'on_chain_end',\n", + " 'name': 'agent',\n", + " 'data': {'input': {'messages': [{'role': 'human',\n", + " 'content': 'whats the weather in sf'}]},\n", + " 'output': {'messages': [{'id': 'run-4885d5a0-cd89-4f00-8558-e85b542a710c',\n", + " 'name': None,\n", + " 'type': 'ai',\n", + " 'content': [{'id': 'toolu_018yyEfJHihdVfWypRNLqDug',\n", + " 'name': 'tavily_search_results_json',\n", + " 'type': 'tool_use',\n", + " 'input': {'query': 'weather in san francisco'}}],\n", + " 'example': False,\n", + " 'tool_calls': [{'id': 'toolu_018yyEfJHihdVfWypRNLqDug',\n", + " 'args': {'query': 'weather in san francisco'},\n", + " 'name': 'tavily_search_results_json'}],\n", + " 'additional_kwargs': {},\n", + " 'response_metadata': {},\n", + " 'invalid_tool_calls': []}]}},\n", + " 'metadata': {'graph_id': 'agent',\n", + " 'thread_id': '6ada015b-b47a-4c4f-a5cd-580893cb6d0c',\n", + " 'created_by': 'system',\n", + " 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'},\n", + " 'tags': ['graph:step:1']},\n", + " {'event_id': 'bbe33ebf-04ba-43d5-8718-e2da23295675',\n", + " 'run_id': 'e843abcb-e478-421b-91e1-a8ae171b14f4',\n", + " 'received_at': '2024-05-18T00:50:29.566076+00:00',\n", + " 'span_id': 'f469ac2e-17a3-491c-bc03-0c56aa30a68b',\n", + " 'event': 'on_chain_stream',\n", + " 'name': 'agent',\n", + " 'data': {'chunk': {'messages': [{'id': 'run-4885d5a0-cd89-4f00-8558-e85b542a710c',\n", + " 'name': None,\n", + " 'type': 'ai',\n", + " 'content': [{'id': 'toolu_018yyEfJHihdVfWypRNLqDug',\n", + " 'name': 'tavily_search_results_json',\n", + " 'type': 'tool_use',\n", + " 'input': {'query': 'weather in san francisco'}}],\n", + " 'example': False,\n", + " 'tool_calls': [{'id': 'toolu_018yyEfJHihdVfWypRNLqDug',\n", + " 'args': {'query': 'weather in san francisco'},\n", + " 'name': 'tavily_search_results_json'}],\n", + " 'additional_kwargs': {},\n", + " 'response_metadata': {},\n", + " 'invalid_tool_calls': []}]}},\n", + " 'metadata': {'graph_id': 'agent',\n", + " 'thread_id': '6ada015b-b47a-4c4f-a5cd-580893cb6d0c',\n", + " 'created_by': 'system',\n", + " 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'},\n", + " 'tags': ['graph:step:1']},\n", + " {'event_id': '38333127-fa97-4830-8157-f76264778d81',\n", + " 'run_id': 'e843abcb-e478-421b-91e1-a8ae171b14f4',\n", + " 'received_at': '2024-05-18T00:50:29.564195+00:00',\n", + " 'span_id': 'ef55c681-be15-4f3d-9aee-3a9ff05d8746',\n", + " 'event': 'on_chain_end',\n", + " 'name': 'should_continue',\n", + " 'data': {'input': {'messages': [{'id': 'abc3581e-417b-4ca1-ab31-de7108e64b3b',\n", + " 'name': None,\n", + " 'type': 'human',\n", + " 'content': 'whats the weather in sf',\n", + " 'example': False,\n", + " 'additional_kwargs': {},\n", + " 'response_metadata': {}},\n", + " {'id': 'run-4885d5a0-cd89-4f00-8558-e85b542a710c',\n", + " 'name': None,\n", + " 'type': 'ai',\n", + " 'content': [{'id': 'toolu_018yyEfJHihdVfWypRNLqDug',\n", + " 'name': 'tavily_search_results_json',\n", + " 'type': 'tool_use',\n", + " 'input': {'query': 'weather in san francisco'}}],\n", + " 'example': False,\n", + " 'tool_calls': [{'id': 'toolu_018yyEfJHihdVfWypRNLqDug',\n", + " 'args': {'query': 'weather in san francisco'},\n", + " 'name': 'tavily_search_results_json'}],\n", + " 'additional_kwargs': {},\n", + " 'response_metadata': {},\n", + " 'invalid_tool_calls': []}]},\n", + " 'output': 'continue'},\n", + " 'metadata': {'graph_id': 'agent',\n", + " 'thread_id': '6ada015b-b47a-4c4f-a5cd-580893cb6d0c',\n", + " 'created_by': 'system',\n", + " 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'},\n", + " 'tags': ['seq:step:3']},\n", + " {'event_id': '408a7785-c715-4bcb-a5aa-950f414baa77',\n", + " 'run_id': 'e843abcb-e478-421b-91e1-a8ae171b14f4',\n", + " 'received_at': '2024-05-18T00:50:29.563289+00:00',\n", + " 'span_id': 'ef55c681-be15-4f3d-9aee-3a9ff05d8746',\n", + " 'event': 'on_chain_start',\n", + " 'name': 'should_continue',\n", + " 'data': {'input': {'messages': [{'id': 'abc3581e-417b-4ca1-ab31-de7108e64b3b',\n", + " 'name': None,\n", + " 'type': 'human',\n", + " 'content': 'whats the weather in sf',\n", + " 'example': False,\n", + " 'additional_kwargs': {},\n", + " 'response_metadata': {}},\n", + " {'id': 'run-4885d5a0-cd89-4f00-8558-e85b542a710c',\n", + " 'name': None,\n", + " 'type': 'ai',\n", + " 'content': [{'id': 'toolu_018yyEfJHihdVfWypRNLqDug',\n", + " 'name': 'tavily_search_results_json',\n", + " 'type': 'tool_use',\n", + " 'input': {'query': 'weather in san francisco'}}],\n", + " 'example': False,\n", + " 'tool_calls': [{'id': 'toolu_018yyEfJHihdVfWypRNLqDug',\n", + " 'args': {'query': 'weather in san francisco'},\n", + " 'name': 'tavily_search_results_json'}],\n", + " 'additional_kwargs': {},\n", + " 'response_metadata': {},\n", + " 'invalid_tool_calls': []}]}},\n", + " 'metadata': {'graph_id': 'agent',\n", + " 'thread_id': '6ada015b-b47a-4c4f-a5cd-580893cb6d0c',\n", + " 'created_by': 'system',\n", + " 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'},\n", + " 'tags': ['seq:step:3']},\n", + " {'event_id': '679c8ae7-5cd2-4462-936e-20f0ea45cfb8',\n", + " 'run_id': 'e843abcb-e478-421b-91e1-a8ae171b14f4',\n", + " 'received_at': '2024-05-18T00:50:29.560628+00:00',\n", + " 'span_id': '4885d5a0-cd89-4f00-8558-e85b542a710c',\n", + " 'event': 'on_chat_model_end',\n", + " 'name': 'ChatAnthropic',\n", + " 'data': {'input': {'messages': [[{'id': None,\n", + " 'name': None,\n", + " 'type': 'human',\n", + " 'content': 'whats the weather in sf',\n", + " 'example': False,\n", + " 'additional_kwargs': {},\n", + " 'response_metadata': {}}]]},\n", + " 'output': {'run': None,\n", + " 'llm_output': None,\n", + " 'generations': [[{'text': '',\n", + " 'type': 'ChatGeneration',\n", + " 'message': {'id': 'run-4885d5a0-cd89-4f00-8558-e85b542a710c',\n", + " 'name': None,\n", + " 'type': 'ai',\n", + " 'content': [{'id': 'toolu_018yyEfJHihdVfWypRNLqDug',\n", + " 'name': 'tavily_search_results_json',\n", + " 'type': 'tool_use',\n", + " 'input': {'query': 'weather in san francisco'}}],\n", + " 'example': False,\n", + " 'tool_calls': [{'id': 'toolu_018yyEfJHihdVfWypRNLqDug',\n", + " 'args': {'query': 'weather in san francisco'},\n", + " 'name': 'tavily_search_results_json'}],\n", + " 'additional_kwargs': {},\n", + " 'response_metadata': {},\n", + " 'invalid_tool_calls': []},\n", + " 'generation_info': None}]]}},\n", + " 'metadata': {'graph_id': 'agent',\n", + " 'thread_id': '6ada015b-b47a-4c4f-a5cd-580893cb6d0c',\n", + " 'created_by': 'system',\n", + " 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca',\n", + " 'ls_model_type': 'chat'},\n", + " 'tags': ['seq:step:1']},\n", + " {'event_id': '055fcf73-36e7-444b-990d-6263ec50925c',\n", + " 'run_id': 'e843abcb-e478-421b-91e1-a8ae171b14f4',\n", + " 'received_at': '2024-05-18T00:50:29.559491+00:00',\n", + " 'span_id': '4885d5a0-cd89-4f00-8558-e85b542a710c',\n", + " 'event': 'on_chat_model_stream',\n", + " 'name': 'ChatAnthropic',\n", + " 'data': {'chunk': {'id': 'run-4885d5a0-cd89-4f00-8558-e85b542a710c',\n", + " 'name': None,\n", + " 'type': 'AIMessageChunk',\n", + " 'content': [{'id': 'toolu_018yyEfJHihdVfWypRNLqDug',\n", + " 'name': 'tavily_search_results_json',\n", + " 'type': 'tool_use',\n", + " 'input': {'query': 'weather in san francisco'}}],\n", + " 'example': False,\n", + " 'tool_calls': [{'id': 'toolu_018yyEfJHihdVfWypRNLqDug',\n", + " 'args': {'query': 'weather in san francisco'},\n", + " 'name': 'tavily_search_results_json'}],\n", + " 'tool_call_chunks': [{'id': 'toolu_018yyEfJHihdVfWypRNLqDug',\n", + " 'args': '{\"query\": \"weather in san francisco\"}',\n", + " 'name': 'tavily_search_results_json',\n", + " 'index': 0}],\n", + " 'additional_kwargs': {},\n", + " 'response_metadata': {},\n", + " 'invalid_tool_calls': []}},\n", + " 'metadata': {'graph_id': 'agent',\n", + " 'thread_id': '6ada015b-b47a-4c4f-a5cd-580893cb6d0c',\n", + " 'created_by': 'system',\n", + " 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca',\n", + " 'ls_model_type': 'chat'},\n", + " 'tags': ['seq:step:1']},\n", + " {'event_id': 'cf90f755-5a12-46bd-8f60-adc862388635',\n", + " 'run_id': 'e843abcb-e478-421b-91e1-a8ae171b14f4',\n", + " 'received_at': '2024-05-18T00:50:27.873602+00:00',\n", + " 'span_id': '4885d5a0-cd89-4f00-8558-e85b542a710c',\n", + " 'event': 'on_chat_model_start',\n", + " 'name': 'ChatAnthropic',\n", + " 'data': {'input': {'messages': [[{'id': None,\n", + " 'name': None,\n", + " 'type': 'human',\n", + " 'content': 'whats the weather in sf',\n", + " 'example': False,\n", + " 'additional_kwargs': {},\n", + " 'response_metadata': {}}]]}},\n", + " 'metadata': {'graph_id': 'agent',\n", + " 'thread_id': '6ada015b-b47a-4c4f-a5cd-580893cb6d0c',\n", + " 'created_by': 'system',\n", + " 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca',\n", + " 'ls_model_type': 'chat'},\n", + " 'tags': ['seq:step:1']}]" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# We can list events for the run\n", + "await client.runs.list_events(thread['thread_id'], run['run_id'])" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "8fa206ed-515e-4607-9a80-bebafe76cc24", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'run_id': 'e843abcb-e478-421b-91e1-a8ae171b14f4',\n", + " 'thread_id': '6ada015b-b47a-4c4f-a5cd-580893cb6d0c',\n", + " 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca',\n", + " 'created_at': '2024-05-18T00:50:27.618761+00:00',\n", + " 'updated_at': '2024-05-18T00:50:27.618761+00:00',\n", + " 'status': 'success',\n", + " 'metadata': {}}" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Eventually, it should finish and we should see `status=success`\n", + "await client.runs.get(thread['thread_id'], run['run_id'])" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "8de4495f-7873-487c-b1a8-ad2a78a1ff35", + "metadata": {}, + "outputs": [], + "source": [ + "# We can get the final results\n", + "results = await client.runs.list_events(thread['thread_id'], run['run_id'])" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "9da76fce-66e4-4f1b-8c24-09759889e50e", + "metadata": {}, + "outputs": [], + "source": [ + "# The results are sorted by time, so the most recent (final) step is the 0 index\n", + "final_result = results[0]" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "02279ff3-c153-4ec4-be4d-1613a0dff4ee", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'event_id': '1af2076e-8ec7-4f2e-bc2c-6fbbf586397c',\n", + " 'run_id': 'e843abcb-e478-421b-91e1-a8ae171b14f4',\n", + " 'received_at': '2024-05-18T00:50:35.557925+00:00',\n", + " 'span_id': 'e843abcb-e478-421b-91e1-a8ae171b14f4',\n", + " 'event': 'on_chain_end',\n", + " 'name': 'LangGraph',\n", + " 'data': {'output': {'messages': [{'id': '46b31c3a-01bf-4946-bd5a-fa6a7f6c97ce',\n", + " 'name': None,\n", + " 'type': 'human',\n", + " 'content': 'whats the weather in sf',\n", + " 'example': False,\n", + " 'additional_kwargs': {},\n", + " 'response_metadata': {}},\n", + " {'id': 'run-4885d5a0-cd89-4f00-8558-e85b542a710c',\n", + " 'name': None,\n", + " 'type': 'ai',\n", + " 'content': [{'id': 'toolu_018yyEfJHihdVfWypRNLqDug',\n", + " 'name': 'tavily_search_results_json',\n", + " 'type': 'tool_use',\n", + " 'input': {'query': 'weather in san francisco'}}],\n", + " 'example': False,\n", + " 'tool_calls': [{'id': 'toolu_018yyEfJHihdVfWypRNLqDug',\n", + " 'args': {'query': 'weather in san francisco'},\n", + " 'name': 'tavily_search_results_json'}],\n", + " 'additional_kwargs': {},\n", + " 'response_metadata': {},\n", + " 'invalid_tool_calls': []},\n", + " {'id': '045e936d-ee47-4236-95ff-793b6b32b590',\n", + " 'name': 'tavily_search_results_json',\n", + " 'type': 'tool',\n", + " 'content': '[{\"url\": \"https://www.weatherapi.com/\", \"content\": \"{\\'location\\': {\\'name\\': \\'San Francisco\\', \\'region\\': \\'California\\', \\'country\\': \\'United States of America\\', \\'lat\\': 37.78, \\'lon\\': -122.42, \\'tz_id\\': \\'America/Los_Angeles\\', \\'localtime_epoch\\': 1715993410, \\'localtime\\': \\'2024-05-17 17:50\\'}, \\'current\\': {\\'last_updated_epoch\\': 1715993100, \\'last_updated\\': \\'2024-05-17 17:45\\', \\'temp_c\\': 17.8, \\'temp_f\\': 64.0, \\'is_day\\': 1, \\'condition\\': {\\'text\\': \\'Partly cloudy\\', \\'icon\\': \\'//cdn.weatherapi.com/weather/64x64/day/116.png\\', \\'code\\': 1003}, \\'wind_mph\\': 15.0, \\'wind_kph\\': 24.1, \\'wind_degree\\': 300, \\'wind_dir\\': \\'WNW\\', \\'pressure_mb\\': 1013.0, \\'pressure_in\\': 29.9, \\'precip_mm\\': 0.0, \\'precip_in\\': 0.0, \\'humidity\\': 65, \\'cloud\\': 25, \\'feelslike_c\\': 17.8, \\'feelslike_f\\': 64.0, \\'vis_km\\': 16.0, \\'vis_miles\\': 9.0, \\'uv\\': 5.0, \\'gust_mph\\': 16.2, \\'gust_kph\\': 26.1}}\"}]',\n", + " 'tool_call_id': 'toolu_018yyEfJHihdVfWypRNLqDug',\n", + " 'additional_kwargs': {},\n", + " 'response_metadata': {}},\n", + " {'id': 'run-069f1ddd-64b3-451d-bb84-75dec23a7286',\n", + " 'name': None,\n", + " 'type': 'ai',\n", + " 'content': \"The search results provide the current weather conditions in San Francisco. According to the data, as of 5:45pm on May 17, 2024, the weather in San Francisco is partly cloudy with a temperature of around 64°F (17.8°C). The wind is blowing from the west-northwest at 15 mph (24 km/h) with gusts up to 16 mph (26 km/h). The humidity is 65% and visibility is 9 miles (16 km). The UV index is 5.\\n\\nSo in summary, it's a partly cloudy spring day in San Francisco with mild temperatures and moderate winds. The weather seems pleasant for being outdoors during the daytime hours.\",\n", + " 'example': False,\n", + " 'tool_calls': [],\n", + " 'additional_kwargs': {},\n", + " 'response_metadata': {},\n", + " 'invalid_tool_calls': []}]}},\n", + " 'metadata': {'graph_id': 'agent',\n", + " 'thread_id': '6ada015b-b47a-4c4f-a5cd-580893cb6d0c',\n", + " 'created_by': 'system',\n", + " 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'},\n", + " 'tags': []}" + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "final_result" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "ddd6e698-4609-4389-b84a-bb8939fff08b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "\"The search results provide the current weather conditions in San Francisco. According to the data, as of 5:45pm on May 17, 2024, the weather in San Francisco is partly cloudy with a temperature of around 64°F (17.8°C). The wind is blowing from the west-northwest at 15 mph (24 km/h) with gusts up to 16 mph (26 km/h). The humidity is 65% and visibility is 9 miles (16 km). The UV index is 5.\\n\\nSo in summary, it's a partly cloudy spring day in San Francisco with mild temperatures and moderate winds. The weather seems pleasant for being outdoors during the daytime hours.\"" + ] + }, + "execution_count": 28, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# We can get the content of the final message\n", + "final_result['data']['output']['messages'][-1]['content']" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "535638f7-48a9-49bb-9a0b-57a5b36d0696", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.1" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/cloud_examples/configuration_cloud.ipynb b/examples/cloud_examples/configuration_cloud.ipynb new file mode 100644 index 000000000..3d427bd0d --- /dev/null +++ b/examples/cloud_examples/configuration_cloud.ipynb @@ -0,0 +1,200 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "68c0837d-c40a-4209-9f88-5d08c00c31b0", + "metadata": {}, + "source": [ + "# How to create agents with configuration\n", + "\n", + "One of the benefits of LangGraph API is that it lets you create agents with different configurations.\n", + "This is useful when you want to:\n", + "\n", + "- Define a cognitive architecture once as a LangGraph\n", + "- Let that LangGraph be configurable across some attributes (for example, system message or LLM to use)\n", + "- Let users create agents with arbitrary configurations, save them, and then use them in the future\n", + "\n", + "In this guide we will show how to do that for the default agent we have built in.\n", + "\n", + "If you look at the agent we defined, you can see that inside the `call_model` node we have created the model based on some configuration. That node looks like:\n", + "\n", + "```python\n", + "def call_model(state, config):\n", + " messages = state[\"messages\"]\n", + " model_name = config.get('configurable', {}).get(\"model_name\", \"anthropic\")\n", + " model = _get_model(model_name)\n", + " response = model.invoke(messages)\n", + " # We return a list, because this will get added to the existing list\n", + " return {\"messages\": [response]}\n", + "```\n", + "\n", + "We are looking inside the config for a `model_name` parameter (which defaults to `anthropic` if none is found).\n", + "That means that by default we are using Anthropic as our model provider.\n", + "In this example we will see an example of how to create an example agent that is configured to use OpenAI.\n", + "\n", + "We've also communicated to the graph that it should expect configuration with this key. \n", + "We've done this by passing `config_schema` when constructing the graph, eg:\n", + "\n", + "```python\n", + "class GraphConfig(TypedDict):\n", + " model_name: Literal[\"anthropic\", \"openai\"]\n", + "\n", + "\n", + "# Define a new graph\n", + "workflow = StateGraph(AgentState, config_schema=GraphConfig)\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "f69c9a4f-2ef9-4998-827b-fe86d12bfd76", + "metadata": {}, + "outputs": [], + "source": [ + "from langgraph_sdk import get_client\n", + "\n", + "client = get_client()" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "9a37bfb5-7331-4004-8054-508838e54f18", + "metadata": {}, + "outputs": [], + "source": [ + "# First, let's check what valid configuration can be\n", + "# We can do this by getting the default assistant\n", + "# There should always be a default assistant with no configuration\n", + "assistants = await client.assistants.search()\n", + "assistants = [a for a in assistants if not a['config']]\n", + "base_assistant = assistants[0]" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "70193a08-127c-44b3-a102-10db260d7e3b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'model_name': {'title': 'Model Name',\n", + " 'enum': ['anthropic', 'openai'],\n", + " 'type': 'string'}}" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# We can now call `.get_schemas` to get schemas associated with this graph\n", + "schemas = await client.assistants.get_schemas(assistant_id=base_assistant[\"assistant_id\"])\n", + "# There are multiple types of schemas\n", + "# We can get the `config_schema` to look at the the configurable parameters\n", + "schemas['config_schema']['definitions']['Configurable']['properties']" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "99be5aee-9a6b-4515-b72f-ba135a893c65", + "metadata": {}, + "outputs": [], + "source": [ + "assistant = await client.assistants.create(graph_id=\"agent\", config={\"configurable\": {\"model_name\": \"openai\"}})" + ] + }, + { + "cell_type": "markdown", + "id": "4f10d346-69e6-44f4-8ff0-ef539ba938df", + "metadata": {}, + "source": [ + "We can see that this assistant has saved the config" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "3898ca35-eb2c-4b12-97ea-e0cc6a7c6a2e", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'assistant_id': '40a3a2bf-5319-4fae-a2ac-05e075615cdc',\n", + " 'graph_id': 'agent',\n", + " 'config': {'configurable': {'model_name': 'openai'}},\n", + " 'created_at': '2024-06-05T23:12:30.519458+00:00',\n", + " 'updated_at': '2024-06-05T23:12:30.519458+00:00',\n", + " 'metadata': {}}" + ] + }, + "execution_count": 20, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "assistant" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "68ed7a1b-74be-4560-8c55-c76d49d3d348", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "StreamPart(event='metadata', data={'run_id': '1ef23911-c23b-6d8c-b1dc-94bb982ca7b1'})\n", + "StreamPart(event='values', data={'messages': [{'role': 'user', 'content': 'who made you?'}]})\n", + "StreamPart(event='values', data={'messages': [{'content': 'who made you?', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': 'ed93c1c9-80d6-4f2b-a048-ef859ea533f9', 'example': False}, {'content': 'I was created by OpenAI, a research organization focused on developing and advancing artificial intelligence technology.', 'additional_kwargs': {}, 'response_metadata': {'finish_reason': 'stop'}, 'type': 'ai', 'name': None, 'id': 'run-6560cd65-5c9c-434b-8835-0baadc684760', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]})\n", + "StreamPart(event='end', data=None)\n" + ] + } + ], + "source": [ + "thread = await client.threads.create()\n", + "input = {\"messages\": [{\"role\": \"user\", \"content\": \"who made you?\"}]}\n", + "async for event in client.runs.stream(thread['thread_id'], assistant['assistant_id'], input=input):\n", + " print(event)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "666d78f1-019a-433e-839e-52d2ebb3d9c8", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.1" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/cloud_examples/enqueue_concurrent.ipynb b/examples/cloud_examples/enqueue_concurrent.ipynb new file mode 100644 index 000000000..90b82c521 --- /dev/null +++ b/examples/cloud_examples/enqueue_concurrent.ipynb @@ -0,0 +1,184 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Enqueue\n", + "\n", + "There are several strategies for handling concurrent runs in your graph. This notebook covers how to use the `enqueue` option - please see the other how-to guides in the \"Double Texting\" directory to learn about the other methods.\n", + "\n", + "First, let's import our required packages and instantiate our client, assistant, and thread." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_core.messages import convert_to_messages\n", + "from langgraph_sdk import get_client" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "client = get_client()\n", + "assistant = await client.assistants.create(\"agent\")\n", + "thread = await client.threads.create()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# this run will be interrupted\n", + "first_run = await client.runs.create(\n", + " thread[\"thread_id\"],\n", + " assistant[\"assistant_id\"],\n", + " input={\"messages\": [{\"role\": \"human\", \"content\": \"whats the weather in sf?\"}]},\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "second_run = await client.runs.create(\n", + " thread[\"thread_id\"],\n", + " assistant[\"assistant_id\"],\n", + " input={\"messages\": [{\"role\": \"human\", \"content\": \"whats the weather in nyc?\"}]},\n", + " multitask_strategy=\"enqueue\",\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Verify that the thread has data from both runs" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# wait until the second run completes\n", + "await client.runs.join(thread[\"thread_id\"], second_run[\"run_id\"])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "state = await client.threads.get_state(thread[\"thread_id\"])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "================================\u001b[1m Human Message \u001b[0m=================================\n", + "\n", + "whats the weather in sf?\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "\n", + "[{'id': 'toolu_01Dez1sJre4oA2Y7NsKJV6VT', 'input': {'query': 'weather in san francisco'}, 'name': 'tavily_search_results_json', 'type': 'tool_use'}]\n", + "Tool Calls:\n", + " tavily_search_results_json (toolu_01Dez1sJre4oA2Y7NsKJV6VT)\n", + " Call ID: toolu_01Dez1sJre4oA2Y7NsKJV6VT\n", + " Args:\n", + " query: weather in san francisco\n", + "=================================\u001b[1m Tool Message \u001b[0m=================================\n", + "Name: tavily_search_results_json\n", + "\n", + "[{\"url\": \"https://www.accuweather.com/en/us/san-francisco/94103/weather-forecast/347629\", \"content\": \"Get the current and future weather conditions for San Francisco, CA, including temperature, precipitation, wind, air quality and more. See the hourly and 10-day outlook, radar maps, alerts and allergy information.\"}]\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "\n", + "According to AccuWeather, the current weather conditions in San Francisco are:\n", + "\n", + "Temperature: 57°F (14°C)\n", + "Conditions: Mostly Sunny\n", + "Wind: WSW 10 mph\n", + "Humidity: 72%\n", + "\n", + "The forecast for the next few days shows partly sunny skies with highs in the upper 50s to mid 60s F (14-18°C) and lows in the upper 40s to low 50s F (9-11°C). Typical mild, dry weather for San Francisco this time of year.\n", + "\n", + "Some key details from the AccuWeather forecast:\n", + "\n", + "Today: Mostly sunny, high of 62°F (17°C)\n", + "Tonight: Partly cloudy, low of 49°F (9°C) \n", + "Tomorrow: Partly sunny, high of 59°F (15°C)\n", + "Saturday: Mostly sunny, high of 64°F (18°C)\n", + "Sunday: Partly sunny, high of 61°F (16°C)\n", + "\n", + "So in summary, expect seasonable spring weather in San Francisco over the next several days, with a mix of sun and clouds and temperatures ranging from the upper 40s at night to the low 60s during the days. Typical dry conditions with no rain in the forecast.\n", + "================================\u001b[1m Human Message \u001b[0m=================================\n", + "\n", + "whats the weather in nyc?\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "\n", + "[{'text': 'Here are the current weather conditions and forecast for New York City:', 'type': 'text'}, {'id': 'toolu_01FFft5Sx9oS6AdVJuRWWcGp', 'input': {'query': 'weather in new york city'}, 'name': 'tavily_search_results_json', 'type': 'tool_use'}]\n", + "Tool Calls:\n", + " tavily_search_results_json (toolu_01FFft5Sx9oS6AdVJuRWWcGp)\n", + " Call ID: toolu_01FFft5Sx9oS6AdVJuRWWcGp\n", + " Args:\n", + " query: weather in new york city\n", + "=================================\u001b[1m Tool Message \u001b[0m=================================\n", + "Name: tavily_search_results_json\n", + "\n", + "[{\"url\": \"https://www.weatherapi.com/\", \"content\": \"{'location': {'name': 'New York', 'region': 'New York', 'country': 'United States of America', 'lat': 40.71, 'lon': -74.01, 'tz_id': 'America/New_York', 'localtime_epoch': 1718734479, 'localtime': '2024-06-18 14:14'}, 'current': {'last_updated_epoch': 1718733600, 'last_updated': '2024-06-18 14:00', 'temp_c': 29.4, 'temp_f': 84.9, 'is_day': 1, 'condition': {'text': 'Sunny', 'icon': '//cdn.weatherapi.com/weather/64x64/day/113.png', 'code': 1000}, 'wind_mph': 2.2, 'wind_kph': 3.6, 'wind_degree': 158, 'wind_dir': 'SSE', 'pressure_mb': 1025.0, 'pressure_in': 30.26, 'precip_mm': 0.0, 'precip_in': 0.0, 'humidity': 63, 'cloud': 0, 'feelslike_c': 31.3, 'feelslike_f': 88.3, 'windchill_c': 28.3, 'windchill_f': 82.9, 'heatindex_c': 29.6, 'heatindex_f': 85.3, 'dewpoint_c': 18.4, 'dewpoint_f': 65.2, 'vis_km': 16.0, 'vis_miles': 9.0, 'uv': 7.0, 'gust_mph': 16.5, 'gust_kph': 26.5}}\"}]\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "\n", + "According to the weather data from WeatherAPI:\n", + "\n", + "Current Conditions in New York City (as of 2:00 PM local time):\n", + "- Temperature: 85°F (29°C)\n", + "- Conditions: Sunny\n", + "- Wind: 2 mph (4 km/h) from the SSE\n", + "- Humidity: 63%\n", + "- Heat Index: 85°F (30°C)\n", + "\n", + "The forecast shows sunny and warm conditions persisting over the next few days:\n", + "\n", + "Today: Sunny, high of 85°F (29°C)\n", + "Tonight: Clear, low of 68°F (20°C)\n", + "Tomorrow: Sunny, high of 88°F (31°C) \n", + "Thursday: Mostly sunny, high of 90°F (32°C)\n", + "Friday: Partly cloudy, high of 87°F (31°C)\n", + "\n", + "So New York City is experiencing beautiful sunny weather with seasonably warm temperatures in the mid-to-upper 80s Fahrenheit (around 30°C). Humidity is moderate in the 60% range. Overall, ideal late spring/early summer conditions for being outdoors in the city over the next several days.\n" + ] + } + ], + "source": [ + "for m in convert_to_messages(state[\"values\"][\"messages\"]):\n", + " m.pretty_print()" + ] + } + ], + "metadata": { + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/examples/cloud_examples/human-in-the-loop_cloud.ipynb b/examples/cloud_examples/human-in-the-loop_cloud.ipynb new file mode 100644 index 000000000..9ab91a93f --- /dev/null +++ b/examples/cloud_examples/human-in-the-loop_cloud.ipynb @@ -0,0 +1,685 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "51466c8d-8ce4-4b3d-be4e-18fdbeda5f53", + "metadata": {}, + "source": [ + "# How to have a human in the loop\n", + "\n", + "With it's built in persistence layer, LangGraph API is perfect for human-in-the-loop workflows.\n", + "Here we cover a few such examples:\n", + "\n", + "1. Having a human in the loop to approve a tool call\n", + "2. Having a human in the loop to edit a tool call\n", + "3. Having a human in the loop to edit an old state and resume execution from there\n" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "521d975b-e94b-4c37-bfa1-82d969e2a4dc", + "metadata": {}, + "outputs": [], + "source": [ + "from langgraph_sdk import get_client" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "27a1392b-86c3-464e-99a8-90ffc965f3ec", + "metadata": {}, + "outputs": [], + "source": [ + "client = get_client()" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "4947e9bc-111f-4991-8c41-1041da9bf0ba", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[{'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca',\n", + " 'graph_id': 'agent',\n", + " 'config': {},\n", + " 'created_at': '2024-05-18T00:19:39.688822+00:00',\n", + " 'updated_at': '2024-05-18T00:19:39.688822+00:00',\n", + " 'metadata': {'created_by': 'system'}}]" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# There should always be a default assistant with no configuration\n", + "assistants = await client.assistants.search()\n", + "assistants = [a for a in assistants if not a['config']]\n", + "assistants" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "230c0464-a6e5-420f-9e38-ca514e5634ce", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca',\n", + " 'graph_id': 'agent',\n", + " 'config': {},\n", + " 'created_at': '2024-05-18T00:19:39.688822+00:00',\n", + " 'updated_at': '2024-05-18T00:19:39.688822+00:00',\n", + " 'metadata': {'created_by': 'system'}}" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "assistant = assistants[0]\n", + "assistant" + ] + }, + { + "cell_type": "markdown", + "id": "e0209129-239b-452e-a59a-47be716bbf8c", + "metadata": {}, + "source": [ + "## Approve a tool call" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "56aa5159-5583-4134-9210-709b969bda6f", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'thread_id': '54ed0901-6767-46c9-a5f9-b65c1c5fd89c',\n", + " 'created_at': '2024-05-18T22:46:16.724701+00:00',\n", + " 'updated_at': '2024-05-18T22:46:16.724701+00:00',\n", + " 'metadata': {}}" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "thread = await client.threads.create()\n", + "thread" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "147c3f98-f889-4f05-a090-6b31f2a0b291", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[]" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "runs = await client.runs.list(thread['thread_id'])\n", + "runs" + ] + }, + { + "cell_type": "markdown", + "id": "77dae6ad-bb7b-468d-b7fd-9b8a35f13ccb", + "metadata": {}, + "source": [ + "We now want to add a human-in-the-loop step before a tool is called.\n", + "We can do this by adding `interrupt_before=[\"action\"]`, which tells us to interrupt before calling the action node.\n", + "We can do this either when compiling the graph or when kicking off a run.\n", + "Here we will do it when kicking of a run." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "7da70e20-1a4e-4df2-b996-1927f474c835", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Receiving new event of type: metadata...\n", + "{'run_id': '3b77ef83-687a-4840-8858-0371f91a92c3'}\n", + "\n", + "\n", + "\n", + "Receiving new event of type: data...\n", + "{'agent': {'messages': [{'content': [{'id': 'toolu_01HwZqM1ptX6E15A5LAmyZTB', 'input': {'query': 'weather in san francisco'}, 'name': 'tavily_search_results_json', 'type': 'tool_use'}], 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-e5d17791-4d37-4ad2-815f-a0c4cba62585', 'example': False, 'tool_calls': [{'name': 'tavily_search_results_json', 'args': {'query': 'weather in san francisco'}, 'id': 'toolu_01HwZqM1ptX6E15A5LAmyZTB'}], 'invalid_tool_calls': []}]}}\n", + "\n", + "\n", + "\n", + "Receiving new event of type: end...\n", + "None\n", + "\n", + "\n", + "\n" + ] + } + ], + "source": [ + "input = {\"messages\": [{\"role\": \"human\", \"content\": \"whats the weather in sf\"}]}\n", + "async for chunk in client.runs.stream(\n", + " thread['thread_id'], assistant['assistant_id'], input=input, stream_mode=\"updates\", interrupt_before=['action']\n", + "):\n", + " print(f\"Receiving new event of type: {chunk.event}...\")\n", + " print(chunk.data)\n", + " print(\"\\n\\n\")" + ] + }, + { + "cell_type": "markdown", + "id": "a36ac0d6-7843-4fab-909c-0b5b6e725a7f", + "metadata": {}, + "source": [ + "We can now kick off a new run on the same thread with `None` as the input in order to just continue the existing thread." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "bded66c7-b56e-4db5-809f-fa5a31d8a012", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Receiving new event of type: metadata...\n", + "{'run_id': 'a46f733d-cf5b-4ee3-9e07-08612468c8df'}\n", + "\n", + "\n", + "\n", + "Receiving new event of type: data...\n", + "{'action': {'messages': [{'content': '[{\"url\": \"https://www.weatherapi.com/\", \"content\": \"{\\'location\\': {\\'name\\': \\'San Francisco\\', \\'region\\': \\'California\\', \\'country\\': \\'United States of America\\', \\'lat\\': 37.78, \\'lon\\': -122.42, \\'tz_id\\': \\'America/Los_Angeles\\', \\'localtime_epoch\\': 1716072201, \\'localtime\\': \\'2024-05-18 15:43\\'}, \\'current\\': {\\'last_updated_epoch\\': 1716071400, \\'last_updated\\': \\'2024-05-18 15:30\\', \\'temp_c\\': 18.9, \\'temp_f\\': 66.0, \\'is_day\\': 1, \\'condition\\': {\\'text\\': \\'Partly cloudy\\', \\'icon\\': \\'//cdn.weatherapi.com/weather/64x64/day/116.png\\', \\'code\\': 1003}, \\'wind_mph\\': 18.6, \\'wind_kph\\': 29.9, \\'wind_degree\\': 280, \\'wind_dir\\': \\'W\\', \\'pressure_mb\\': 1015.0, \\'pressure_in\\': 29.96, \\'precip_mm\\': 0.0, \\'precip_in\\': 0.0, \\'humidity\\': 59, \\'cloud\\': 25, \\'feelslike_c\\': 18.9, \\'feelslike_f\\': 66.0, \\'vis_km\\': 16.0, \\'vis_miles\\': 9.0, \\'uv\\': 5.0, \\'gust_mph\\': 23.0, \\'gust_kph\\': 37.1}}\"}]', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': 'tavily_search_results_json', 'id': '8be98ff3-6d61-41c5-8384-8db6b7abdbfb', 'tool_call_id': 'toolu_01HwZqM1ptX6E15A5LAmyZTB'}]}}\n", + "\n", + "\n", + "\n", + "Receiving new event of type: data...\n", + "{'agent': {'messages': [{'content': \"The weather in San Francisco is currently partly cloudy with a temperature of around 66°F (18.9°C). There are westerly winds of 18.6 mph (29.9 km/h) with gusts up to 23 mph (37.1 km/h). The humidity is 59% and visibility is good at 9 miles (16 km). UV levels are moderate at 5.0.\\n\\nIn summary, it's a nice partly cloudy spring day in San Francisco with comfortable temperatures and a moderate breeze. The weather conditions seem ideal for being outdoors and enjoying the city.\", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-7a8a2ff8-d0d6-4200-b0a5-926f2b6a4798', 'example': False, 'tool_calls': [], 'invalid_tool_calls': []}]}}\n", + "\n", + "\n", + "\n", + "Receiving new event of type: end...\n", + "None\n", + "\n", + "\n", + "\n" + ] + } + ], + "source": [ + "input = None\n", + "async for chunk in client.runs.stream(\n", + " thread['thread_id'], assistant['assistant_id'], input=input, stream_mode=\"updates\", interrupt_before=['action']\n", + "):\n", + " print(f\"Receiving new event of type: {chunk.event}...\")\n", + " print(chunk.data)\n", + " print(\"\\n\\n\")" + ] + }, + { + "cell_type": "markdown", + "id": "2072ce5a-8771-42f9-b2de-5d3a7a9c817b", + "metadata": {}, + "source": [ + "## Edit a tool call\n", + "\n", + "What if we want to edit the tool call?\n", + "We can also do that.\n", + "Let's kick off another run, with the same `interrupt_before=['action']`" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "b226b687-02da-4eef-9286-46dba92b17ba", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Receiving new event of type: metadata...\n", + "{'run_id': 'c7c8e313-dad9-47d9-bd03-e112c94eff9e'}\n", + "\n", + "\n", + "\n", + "Receiving new event of type: data...\n", + "{'agent': {'messages': [{'content': [{'id': 'toolu_01NGhKmeciaT7TfhBSwUT3mi', 'input': {'query': 'weather in los angeles'}, 'name': 'tavily_search_results_json', 'type': 'tool_use'}], 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-3d417aa5-e9c1-4b76-90f8-597519c28af9', 'example': False, 'tool_calls': [{'name': 'tavily_search_results_json', 'args': {'query': 'weather in los angeles'}, 'id': 'toolu_01NGhKmeciaT7TfhBSwUT3mi'}], 'invalid_tool_calls': []}]}}\n", + "\n", + "\n", + "\n", + "Receiving new event of type: end...\n", + "None\n", + "\n", + "\n", + "\n" + ] + } + ], + "source": [ + "input = {\"messages\": [{\"role\": \"human\", \"content\": \"whats the weather in la?\"}]}\n", + "async for chunk in client.runs.stream(\n", + " thread['thread_id'], assistant['assistant_id'], input=input, stream_mode=\"updates\", interrupt_before=['action']\n", + "):\n", + " print(f\"Receiving new event of type: {chunk.event}...\")\n", + " print(chunk.data)\n", + " print(\"\\n\\n\")" + ] + }, + { + "cell_type": "markdown", + "id": "ab338423-c18d-446c-9aa3-3ad2f16d742a", + "metadata": {}, + "source": [ + "We can now inspect the state of the thread" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "bd9ca1f4-c3b0-4fa3-8c91-233a9129a142", + "metadata": {}, + "outputs": [], + "source": [ + "thread_state = await client.threads.get_state(thread['thread_id'])" + ] + }, + { + "cell_type": "markdown", + "id": "31e82414-afd2-46c4-a605-ce3eb46df485", + "metadata": {}, + "source": [ + "Let's get the last message of the thread - this is the one we want to update" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "fe832ec1-7ae0-4d11-8408-d4da88d4dced", + "metadata": {}, + "outputs": [], + "source": [ + "last_message = thread_state['values']['messages'][-1]" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "434253fe-7397-45e2-8be8-91d002088a96", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[{'id': 'toolu_01NGhKmeciaT7TfhBSwUT3mi',\n", + " 'input': {'query': 'weather in los angeles'},\n", + " 'name': 'tavily_search_results_json',\n", + " 'type': 'tool_use'}]" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "last_message['content']" + ] + }, + { + "cell_type": "markdown", + "id": "6d007b31-c8a2-465c-bc78-a5909ca7931c", + "metadata": {}, + "source": [ + "Let's now modify the tool call to say Louisiana" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "55fcb316-450b-4b8c-9ae9-e7ee395acc55", + "metadata": {}, + "outputs": [], + "source": [ + "last_message['tool_calls'] = [{\n", + " 'id': last_message['tool_calls'][0]['id'],\n", + " 'name': 'tavily_search_results_json',\n", + " # We change the query to say temperature\n", + " 'args': {'query': 'weather in Louisiana'}\n", + "}]\n", + "# last_message['content'] = [{\n", + "# 'id': last_message['content'][0]['id'],\n", + "# 'name': 'tavily_search_results_json',\n", + "# # We change the query to say temperature\n", + "# 'input': {'query': 'weather in Louisiana'},\n", + "# 'type': 'tool_use'\n", + "# }]" + ] + }, + { + "cell_type": "markdown", + "id": "d49be54e-5334-47be-8dfb-78b8a8155e98", + "metadata": {}, + "source": [ + "We can now update the state - we only need to pass in the last updated message because our graph will handle the update." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "0438f997-bad3-48f6-b532-9ac3a95263c2", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'configurable': {'thread_id': '54ed0901-6767-46c9-a5f9-b65c1c5fd89c',\n", + " 'thread_ts': '1ef15688-1dbd-68f5-8007-75dc0e110124'}}" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "await client.threads.update_state(thread['thread_id'], values={\"messages\": [last_message]})" + ] + }, + { + "cell_type": "markdown", + "id": "c96668ab-80fa-4ae6-a90b-773a943ba331", + "metadata": {}, + "source": [ + "Let's now check the state of the thread again, and in particular the final message" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "31936711-4af4-4bd1-ac10-9ce52922dd2f", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[{'name': 'tavily_search_results_json',\n", + " 'args': {'query': 'weather in Louisiana'},\n", + " 'id': 'toolu_01NGhKmeciaT7TfhBSwUT3mi'}]" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "thread_state = await client.threads.get_state(thread['thread_id'])\n", + "thread_state['values']['messages'][-1]['tool_calls']" + ] + }, + { + "cell_type": "markdown", + "id": "20aa8ff3-7876-4db2-9333-c5396cd637ac", + "metadata": {}, + "source": [ + "Great! We changed it. If we now resume execution (by kicking off a new run with null inputs on the same thread) it should use that new tool call." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "8e2c4eeb-2888-4979-9877-aa4a53dec5ea", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Receiving new event of type: metadata...\n", + "{'run_id': '1a1ebed1-3581-418a-81be-e834b40c5c82'}\n", + "\n", + "\n", + "\n", + "Receiving new event of type: data...\n", + "{'action': {'messages': [{'content': '[{\"url\": \"https://www.weatherapi.com/\", \"content\": \"{\\'location\\': {\\'name\\': \\'Louisiana\\', \\'region\\': \\'Missouri\\', \\'country\\': \\'USA United States of America\\', \\'lat\\': 39.44, \\'lon\\': -91.06, \\'tz_id\\': \\'America/Chicago\\', \\'localtime_epoch\\': 1716072393, \\'localtime\\': \\'2024-05-18 17:46\\'}, \\'current\\': {\\'last_updated_epoch\\': 1716072300, \\'last_updated\\': \\'2024-05-18 17:45\\', \\'temp_c\\': 29.0, \\'temp_f\\': 84.2, \\'is_day\\': 1, \\'condition\\': {\\'text\\': \\'Partly cloudy\\', \\'icon\\': \\'//cdn.weatherapi.com/weather/64x64/day/116.png\\', \\'code\\': 1003}, \\'wind_mph\\': 6.9, \\'wind_kph\\': 11.2, \\'wind_degree\\': 220, \\'wind_dir\\': \\'SW\\', \\'pressure_mb\\': 1011.0, \\'pressure_in\\': 29.86, \\'precip_mm\\': 0.0, \\'precip_in\\': 0.0, \\'humidity\\': 46, \\'cloud\\': 50, \\'feelslike_c\\': 31.4, \\'feelslike_f\\': 88.6, \\'vis_km\\': 16.0, \\'vis_miles\\': 9.0, \\'uv\\': 7.0, \\'gust_mph\\': 7.4, \\'gust_kph\\': 11.9}}\"}]', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': 'tavily_search_results_json', 'id': '728f8ac9-729e-4bf7-b560-b332a73c8f47', 'tool_call_id': 'toolu_01NGhKmeciaT7TfhBSwUT3mi'}]}}\n", + "\n", + "\n", + "\n", + "Receiving new event of type: data...\n", + "{'agent': {'messages': [{'content': [{'text': 'The search results seem to be for the weather in Louisiana, Missouri rather than Los Angeles, California. Let me try the search again:', 'type': 'text'}, {'id': 'toolu_019YAXWMK33tG9DaxMzrowc8', 'input': {'query': 'weather in los angeles california'}, 'name': 'tavily_search_results_json', 'type': 'tool_use'}], 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-c42a3b14-2611-4a1d-8907-95dcdb18f07f', 'example': False, 'tool_calls': [{'name': 'tavily_search_results_json', 'args': {'query': 'weather in los angeles california'}, 'id': 'toolu_019YAXWMK33tG9DaxMzrowc8'}], 'invalid_tool_calls': []}]}}\n", + "\n", + "\n", + "\n", + "Receiving new event of type: end...\n", + "None\n", + "\n", + "\n", + "\n" + ] + } + ], + "source": [ + "input = None\n", + "async for chunk in client.runs.stream(\n", + " thread['thread_id'], assistant['assistant_id'], input=input, stream_mode=\"updates\", interrupt_before=['action']\n", + "):\n", + " print(f\"Receiving new event of type: {chunk.event}...\")\n", + " print(chunk.data)\n", + " print(\"\\n\\n\")" + ] + }, + { + "cell_type": "markdown", + "id": "065f8165-43d8-4876-86af-0cfffd712fee", + "metadata": {}, + "source": [ + "## Edit an old state\n", + "\n", + "Let's now imagine we want to go back in time and edit the tool call after we had already made it.\n", + "In order to do this, we can get first get the full history of the thread." + ] + }, + { + "cell_type": "code", + "execution_count": 46, + "id": "de050efd-73a4-441e-91e0-18e08f773a42", + "metadata": {}, + "outputs": [], + "source": [ + "thread_history = await client.threads.get_history(thread['thread_id'], limit=100)" + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "id": "07e15435-4a5f-4c2a-b748-0e0f7ab02a28", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "11" + ] + }, + "execution_count": 47, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "len(thread_history)" + ] + }, + { + "cell_type": "markdown", + "id": "a292e721-36c4-41b8-85e4-378f0770652a", + "metadata": {}, + "source": [ + "After that, we can get the correct state we want to be in. The 0th index state is the most recent one, while the -1 index state is the first.\n", + "In this case, we want to go to the state where the last message had the tool calls for `weather in los angeles`" + ] + }, + { + "cell_type": "code", + "execution_count": 48, + "id": "132d207c-11cb-4efb-a330-88ebdfc612c8", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[{'name': 'tavily_search_results_json',\n", + " 'args': {'query': 'weather in los angeles'},\n", + " 'id': 'toolu_01FnuDKhUfagwoqhNfiTYTfS'}]" + ] + }, + "execution_count": 48, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "rewind_state = thread_history[3]\n", + "rewind_state['values']['messages'][-1]['tool_calls']" + ] + }, + { + "cell_type": "code", + "execution_count": 49, + "id": "45e01ddf-2ccf-4029-b431-e5fce2235b59", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'configurable': {'thread_id': 'df85453d-cb86-48c8-ae84-12081faa1bdf',\n", + " 'thread_ts': '1ef15582-3442-6db7-8006-9166bbb0e80f'}}" + ] + }, + "execution_count": 49, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "rewind_state['config']" + ] + }, + { + "cell_type": "markdown", + "id": "d229468e-2f94-4b29-b56b-1d402554dcfb", + "metadata": {}, + "source": [ + "If we want to, we can now resume execution from that place in time" + ] + }, + { + "cell_type": "code", + "execution_count": 50, + "id": "94ebc63e-f2cf-4da1-bc8d-52c4731ab0c6", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Receiving new event of type: metadata...\n", + "{'run_id': 'a1cc9263-ef0a-4c04-9194-6f01624d0ef0'}\n", + "\n", + "\n", + "\n", + "Receiving new event of type: data...\n", + "{'action': {'messages': [{'content': '[{\"url\": \"https://www.weatherapi.com/\", \"content\": \"{\\'location\\': {\\'name\\': \\'Los Angeles\\', \\'region\\': \\'California\\', \\'country\\': \\'United States of America\\', \\'lat\\': 34.05, \\'lon\\': -118.24, \\'tz_id\\': \\'America/Los_Angeles\\', \\'localtime_epoch\\': 1716071728, \\'localtime\\': \\'2024-05-18 15:35\\'}, \\'current\\': {\\'last_updated_epoch\\': 1716071400, \\'last_updated\\': \\'2024-05-18 15:30\\', \\'temp_c\\': 20.0, \\'temp_f\\': 68.0, \\'is_day\\': 1, \\'condition\\': {\\'text\\': \\'Partly cloudy\\', \\'icon\\': \\'//cdn.weatherapi.com/weather/64x64/day/116.png\\', \\'code\\': 1003}, \\'wind_mph\\': 2.2, \\'wind_kph\\': 3.6, \\'wind_degree\\': 226, \\'wind_dir\\': \\'SW\\', \\'pressure_mb\\': 1016.0, \\'pressure_in\\': 29.99, \\'precip_mm\\': 0.0, \\'precip_in\\': 0.0, \\'humidity\\': 61, \\'cloud\\': 50, \\'feelslike_c\\': 20.0, \\'feelslike_f\\': 68.0, \\'vis_km\\': 16.0, \\'vis_miles\\': 9.0, \\'uv\\': 6.0, \\'gust_mph\\': 12.6, \\'gust_kph\\': 20.3}}\"}]', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': 'tavily_search_results_json', 'id': '7137b2e5-566b-418b-b642-b3c6b64c5224', 'tool_call_id': 'toolu_01FnuDKhUfagwoqhNfiTYTfS'}]}}\n", + "\n", + "\n", + "\n", + "Receiving new event of type: data...\n", + "{'agent': {'messages': [{'content': 'The search results show the current weather conditions in Los Angeles. As of 3:30pm on May 18, 2024, the weather in Los Angeles is partly cloudy with a temperature around 68°F (20°C). Winds are light from the southwest around 2-3 mph. The humidity is 61% and visibility is good at 9 miles. Overall, it appears to be a nice spring day in LA with partly sunny skies and comfortable temperatures in the upper 60s.', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-3966b68a-c381-4933-a852-e6a4697c962c', 'example': False, 'tool_calls': [], 'invalid_tool_calls': []}]}}\n", + "\n", + "\n", + "\n", + "Receiving new event of type: end...\n", + "None\n", + "\n", + "\n", + "\n" + ] + } + ], + "source": [ + "input = None\n", + "async for chunk in client.runs.stream(\n", + " thread['thread_id'], \n", + " assistant['assistant_id'], \n", + " input=input, \n", + " stream_mode=\"updates\", \n", + " interrupt_before=['action'],\n", + " config=rewind_state['config']\n", + "):\n", + " print(f\"Receiving new event of type: {chunk.event}...\")\n", + " print(chunk.data)\n", + " print(\"\\n\\n\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "492f1d37-0979-4210-8dd7-bc70cdc308f3", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.1" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/cloud_examples/interrupt_concurrent.ipynb b/examples/cloud_examples/interrupt_concurrent.ipynb new file mode 100644 index 000000000..0df7a6ff9 --- /dev/null +++ b/examples/cloud_examples/interrupt_concurrent.ipynb @@ -0,0 +1,175 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Interrupt\n", + "\n", + "There are several strategies for handling concurrent runs in your graph. This notebook covers how to use the `interrupt` option - please see the other how-to guides in the \"Double Texting\" directory to learn about the other methods.\n", + "\n", + "First, let's import our required packages and instantiate our client, assistant, and thread." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import asyncio\n", + "\n", + "from langchain_core.messages import convert_to_messages\n", + "from langgraph_sdk import get_client" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "client = get_client()\n", + "assistant = await client.assistants.create(\"agent\")\n", + "thread = await client.threads.create()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# the first run will be interrupted\n", + "interrupted_run = await client.runs.create(\n", + " thread[\"thread_id\"],\n", + " assistant[\"assistant_id\"],\n", + " input={\"messages\": [{\"role\": \"human\", \"content\": \"whats the weather in sf?\"}]},\n", + ")\n", + "await asyncio.sleep(2)\n", + "run = await client.runs.create(\n", + " thread[\"thread_id\"],\n", + " assistant[\"assistant_id\"],\n", + " input={\"messages\": [{\"role\": \"human\", \"content\": \"whats the weather in nyc?\"}]},\n", + " multitask_strategy=\"interrupt\",\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# wait until the second run completes\n", + "await client.runs.join(thread[\"thread_id\"], run[\"run_id\"])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can see that the thread has partial data from the first run + data from the second run" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "state = await client.threads.get_state(thread[\"thread_id\"])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "================================\u001b[1m Human Message \u001b[0m=================================\n", + "\n", + "whats the weather in sf?\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "\n", + "[{'id': 'toolu_01MjNtVJwEcpujRGrf3x6Pih', 'input': {'query': 'weather in san francisco'}, 'name': 'tavily_search_results_json', 'type': 'tool_use'}]\n", + "Tool Calls:\n", + " tavily_search_results_json (toolu_01MjNtVJwEcpujRGrf3x6Pih)\n", + " Call ID: toolu_01MjNtVJwEcpujRGrf3x6Pih\n", + " Args:\n", + " query: weather in san francisco\n", + "=================================\u001b[1m Tool Message \u001b[0m=================================\n", + "Name: tavily_search_results_json\n", + "\n", + "[{\"url\": \"https://www.wunderground.com/hourly/us/ca/san-francisco/KCASANFR2002/date/2024-6-18\", \"content\": \"High 64F. Winds W at 10 to 20 mph. A few clouds from time to time. Low 49F. Winds W at 10 to 20 mph. Temp. San Francisco Weather Forecasts. Weather Underground provides local & long-range weather ...\"}]\n", + "================================\u001b[1m Human Message \u001b[0m=================================\n", + "\n", + "whats the weather in nyc?\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "\n", + "[{'id': 'toolu_01KtE1m1ifPLQAx4fQLyZL9Q', 'input': {'query': 'weather in new york city'}, 'name': 'tavily_search_results_json', 'type': 'tool_use'}]\n", + "Tool Calls:\n", + " tavily_search_results_json (toolu_01KtE1m1ifPLQAx4fQLyZL9Q)\n", + " Call ID: toolu_01KtE1m1ifPLQAx4fQLyZL9Q\n", + " Args:\n", + " query: weather in new york city\n", + "=================================\u001b[1m Tool Message \u001b[0m=================================\n", + "Name: tavily_search_results_json\n", + "\n", + "[{\"url\": \"https://www.accuweather.com/en/us/new-york/10021/june-weather/349727\", \"content\": \"Get the monthly weather forecast for New York, NY, including daily high/low, historical averages, to help you plan ahead.\"}]\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "\n", + "The search results provide weather forecasts and information for New York City. Based on the top result from AccuWeather, here are some key details about the weather in NYC:\n", + "\n", + "- This is a monthly weather forecast for New York City for the month of June.\n", + "- It includes daily high and low temperatures to help plan ahead.\n", + "- Historical averages for June in NYC are also provided as a reference point.\n", + "- More detailed daily or hourly forecasts with precipitation chances, humidity, wind, etc. can be found by visiting the AccuWeather page.\n", + "\n", + "So in summary, the search provides a convenient overview of the expected weather conditions in New York City over the next month to give you an idea of what to prepare for if traveling or making plans there. Let me know if you need any other details!\n" + ] + } + ], + "source": [ + "for m in convert_to_messages(state[\"values\"][\"messages\"]):\n", + " m.pretty_print()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Verify that the original, interrupted run was interrupted" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'interrupted'" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "(await client.runs.get(thread[\"thread_id\"], interrupted_run[\"run_id\"]))[\"status\"]" + ] + } + ], + "metadata": { + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/examples/cloud_examples/reject_concurrent.ipynb b/examples/cloud_examples/reject_concurrent.ipynb new file mode 100644 index 000000000..9aa603b12 --- /dev/null +++ b/examples/cloud_examples/reject_concurrent.ipynb @@ -0,0 +1,172 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Reject\n", + "\n", + "There are several strategies for handling concurrent runs in your graph. This notebook covers how to use the `reject` option - please see the other how-to guides in the \"Double Texting\" directory to learn about the other methods.\n", + "\n", + "First, let's import our required packages and instantiate our client, assistant, and thread." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import httpx\n", + "from langchain_core.messages import convert_to_messages\n", + "from langgraph_sdk import get_client" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "client = get_client()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "assistant = await client.assistants.create(\"agent\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "thread = await client.threads.create()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "run = await client.runs.create(\n", + " thread[\"thread_id\"],\n", + " assistant[\"assistant_id\"],\n", + " input={\"messages\": [{\"role\": \"human\", \"content\": \"whats the weather in sf?\"}]},\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Failed to start concurrent run Client error '409 Conflict' for url 'http://localhost:8123/threads/f9e7088b-8028-4e5c-88d2-9cc9a2870e50/runs'\n", + "For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/409\n" + ] + } + ], + "source": [ + "try:\n", + " await client.runs.create(\n", + " thread[\"thread_id\"],\n", + " assistant[\"assistant_id\"],\n", + " input={\"messages\": [{\"role\": \"human\", \"content\": \"whats the weather in nyc?\"}]},\n", + " multitask_strategy=\"reject\",\n", + " )\n", + "except httpx.HTTPStatusError as e:\n", + " print(\"Failed to start concurrent run\", e)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can verify that the original thread finished executing:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# wait until the original run completes\n", + "await client.runs.join(thread[\"thread_id\"], run[\"run_id\"])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "state = await client.threads.get_state(thread[\"thread_id\"])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "================================\u001b[1m Human Message \u001b[0m=================================\n", + "\n", + "whats the weather in sf?\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "\n", + "[{'id': 'toolu_01CyewEifV2Kmi7EFKHbMDr1', 'input': {'query': 'weather in san francisco'}, 'name': 'tavily_search_results_json', 'type': 'tool_use'}]\n", + "Tool Calls:\n", + " tavily_search_results_json (toolu_01CyewEifV2Kmi7EFKHbMDr1)\n", + " Call ID: toolu_01CyewEifV2Kmi7EFKHbMDr1\n", + " Args:\n", + " query: weather in san francisco\n", + "=================================\u001b[1m Tool Message \u001b[0m=================================\n", + "Name: tavily_search_results_json\n", + "\n", + "[{\"url\": \"https://www.accuweather.com/en/us/san-francisco/94103/june-weather/347629\", \"content\": \"Get the monthly weather forecast for San Francisco, CA, including daily high/low, historical averages, to help you plan ahead.\"}]\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "\n", + "According to the search results from Tavily, the current weather in San Francisco is:\n", + "\n", + "The average high temperature in San Francisco in June is around 65°F (18°C), with average lows around 54°F (12°C). June tends to be one of the cooler and foggier months in San Francisco due to the marine layer of fog that often blankets the city during the summer months.\n", + "\n", + "Some key points about the typical June weather in San Francisco:\n", + "\n", + "- Mild temperatures with highs in the 60s F and lows in the 50s F\n", + "- Foggy mornings that often burn off to sunny afternoons\n", + "- Little to no rainfall, as June falls in the dry season\n", + "- Breezy conditions, with winds off the Pacific Ocean\n", + "- Layers are recommended for changing weather conditions\n", + "\n", + "So in summary, you can expect mild, foggy mornings giving way to sunny but cool afternoons in San Francisco this time of year. The marine layer keeps temperatures moderate compared to other parts of California in June.\n" + ] + } + ], + "source": [ + "for m in convert_to_messages(state[\"values\"][\"messages\"]):\n", + " m.pretty_print()" + ] + } + ], + "metadata": { + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/examples/cloud_examples/rollback_concurrent.ipynb b/examples/cloud_examples/rollback_concurrent.ipynb new file mode 100644 index 000000000..37d1ab9f5 --- /dev/null +++ b/examples/cloud_examples/rollback_concurrent.ipynb @@ -0,0 +1,155 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Rollback\n", + "\n", + "There are several strategies for handling concurrent runs in your graph. This notebook covers how to use the `rollback` option - please see the other how-to guides in the \"Double Texting\" directory to learn about the other methods.\n", + "\n", + "First, let's import our required packages and instantiate our client, assistant, and thread." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import asyncio\n", + "\n", + "import httpx\n", + "from langchain_core.messages import convert_to_messages\n", + "from langgraph_sdk import get_client" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "client = get_client()\n", + "assistant = await client.assistants.create(\"agent\")\n", + "thread = await client.threads.create()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# the first run will be interrupted\n", + "rolled_back_run = await client.runs.create(\n", + " thread[\"thread_id\"],\n", + " assistant[\"assistant_id\"],\n", + " input={\"messages\": [{\"role\": \"human\", \"content\": \"whats the weather in sf?\"}]},\n", + ")\n", + "await asyncio.sleep(2)\n", + "run = await client.runs.create(\n", + " thread[\"thread_id\"],\n", + " assistant[\"assistant_id\"],\n", + " input={\"messages\": [{\"role\": \"human\", \"content\": \"whats the weather in nyc?\"}]},\n", + " multitask_strategy=\"rollback\",\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# wait until the second run completes\n", + "await client.runs.join(thread[\"thread_id\"], run[\"run_id\"])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can see that the thread has data only from the second run" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "state = await client.threads.get_state(thread[\"thread_id\"])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "================================\u001b[1m Human Message \u001b[0m=================================\n", + "\n", + "whats the weather in nyc?\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "\n", + "[{'id': 'toolu_01JzPqefao1gxwajHQ3Yh3JD', 'input': {'query': 'weather in nyc'}, 'name': 'tavily_search_results_json', 'type': 'tool_use'}]\n", + "Tool Calls:\n", + " tavily_search_results_json (toolu_01JzPqefao1gxwajHQ3Yh3JD)\n", + " Call ID: toolu_01JzPqefao1gxwajHQ3Yh3JD\n", + " Args:\n", + " query: weather in nyc\n", + "=================================\u001b[1m Tool Message \u001b[0m=================================\n", + "Name: tavily_search_results_json\n", + "\n", + "[{\"url\": \"https://www.weatherapi.com/\", \"content\": \"{'location': {'name': 'New York', 'region': 'New York', 'country': 'United States of America', 'lat': 40.71, 'lon': -74.01, 'tz_id': 'America/New_York', 'localtime_epoch': 1718734479, 'localtime': '2024-06-18 14:14'}, 'current': {'last_updated_epoch': 1718733600, 'last_updated': '2024-06-18 14:00', 'temp_c': 29.4, 'temp_f': 84.9, 'is_day': 1, 'condition': {'text': 'Sunny', 'icon': '//cdn.weatherapi.com/weather/64x64/day/113.png', 'code': 1000}, 'wind_mph': 2.2, 'wind_kph': 3.6, 'wind_degree': 158, 'wind_dir': 'SSE', 'pressure_mb': 1025.0, 'pressure_in': 30.26, 'precip_mm': 0.0, 'precip_in': 0.0, 'humidity': 63, 'cloud': 0, 'feelslike_c': 31.3, 'feelslike_f': 88.3, 'windchill_c': 28.3, 'windchill_f': 82.9, 'heatindex_c': 29.6, 'heatindex_f': 85.3, 'dewpoint_c': 18.4, 'dewpoint_f': 65.2, 'vis_km': 16.0, 'vis_miles': 9.0, 'uv': 7.0, 'gust_mph': 16.5, 'gust_kph': 26.5}}\"}]\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "\n", + "The weather API results show that the current weather in New York City is sunny with a temperature of around 85°F (29°C). The wind is light at around 2-3 mph from the south-southeast. Overall it looks like a nice sunny summer day in NYC.\n" + ] + } + ], + "source": [ + "for m in convert_to_messages(state[\"values\"][\"messages\"]):\n", + " m.pretty_print()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Verify that the original, rolled back run was deleted" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Original run was correctly deleted\n" + ] + } + ], + "source": [ + "try:\n", + " await client.runs.get(thread[\"thread_id\"], rolled_back_run[\"run_id\"])\n", + "except httpx.HTTPStatusError as _:\n", + " print(\"Original run was correctly deleted\")" + ] + } + ], + "metadata": { + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/examples/cloud_examples/same-thread.ipynb b/examples/cloud_examples/same-thread.ipynb new file mode 100644 index 000000000..94d08a04e --- /dev/null +++ b/examples/cloud_examples/same-thread.ipynb @@ -0,0 +1,184 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "68c0837d-c40a-4209-9f88-5d08c00c31b0", + "metadata": {}, + "source": [ + "# How to run multiple agents on the same thread\n", + "\n", + "In LangGraph API, a thread is not explicitly associated with a particular agent.\n", + "This means that you can run multiple agents on the same thread.\n", + "In this example, we will create two agents and then call them both on the same thread." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "e06be1f6-07a5-4e93-8497-02473fc65d4f", + "metadata": {}, + "outputs": [], + "source": [ + "from langgraph_sdk import get_client\n", + "\n", + "client = get_client()\n", + "\n", + "openai_assistant = await client.assistants.create(graph_id=\"agent\", config={\"configurable\": {\"model_name\": \"openai\"}})\n", + "\n", + "# There should always be a default assistant with no configuration\n", + "assistants = await client.assistants.search()\n", + "default_assistant = [a for a in assistants if not a['config']][0]" + ] + }, + { + "cell_type": "markdown", + "id": "4f10d346-69e6-44f4-8ff0-ef539ba938df", + "metadata": {}, + "source": [ + "We can see that these agents are different" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "3898ca35-eb2c-4b12-97ea-e0cc6a7c6a2e", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'assistant_id': '13ecc353-a9a9-474b-a824-b6a343cd74b1',\n", + " 'graph_id': 'agent',\n", + " 'config': {'configurable': {'model_name': 'openai'}},\n", + " 'created_at': '2024-05-21T16:22:59.258447+00:00',\n", + " 'updated_at': '2024-05-21T16:22:59.258447+00:00',\n", + " 'metadata': {}}" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "openai_assistant" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "a8fa67b2-cb4f-43d3-a1fc-f8b3936c16b6", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca',\n", + " 'graph_id': 'agent',\n", + " 'config': {},\n", + " 'created_at': '2024-05-18T00:19:39.688822+00:00',\n", + " 'updated_at': '2024-05-18T00:19:39.688822+00:00',\n", + " 'metadata': {'created_by': 'system'}}" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "default_assistant" + ] + }, + { + "cell_type": "markdown", + "id": "5e655e61-c2ee-488a-90f6-6189c84841da", + "metadata": {}, + "source": [ + "We can now run it on the OpenAI assistant first." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "68ed7a1b-74be-4560-8c55-c76d49d3d348", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "StreamPart(event='metadata', data={'run_id': 'f90b3029-8669-4d70-976c-b70368e355d8'})\n", + "StreamPart(event='updates', data={'agent': {'messages': [{'content': 'I was created by OpenAI, a research organization focused on developing and advancing artificial intelligence technology.', 'additional_kwargs': {}, 'response_metadata': {'finish_reason': 'stop'}, 'type': 'ai', 'name': None, 'id': 'run-9801a5ba-2f3c-43de-89cf-c740debf36fc', 'example': False, 'tool_calls': [], 'invalid_tool_calls': []}]}})\n", + "StreamPart(event='end', data=None)\n" + ] + } + ], + "source": [ + "thread = await client.threads.create()\n", + "input = {\"messages\": [{\"role\": \"user\", \"content\": \"who made you?\"}]}\n", + "async for event in client.runs.stream(thread['thread_id'], openai_assistant['assistant_id'], input=input, stream_mode='updates'):\n", + " print(event)" + ] + }, + { + "cell_type": "markdown", + "id": "c53709e9-ddb2-4429-9042-456eb6c91244", + "metadata": {}, + "source": [ + "Now, we can run it on a different Anthropic-based assistant." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "666d78f1-019a-433e-839e-52d2ebb3d9c8", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "StreamPart(event='metadata', data={'run_id': 'c3521302-48ae-4c29-a0f2-5eb865cbc6d7'})\n", + "StreamPart(event='updates', data={'agent': {'messages': [{'content': \"I am an AI assistant created by Anthropic to be helpful, harmless, and honest. I don't actually have a physical form or visual representation - I exist as a language model trained to have natural conversations.\", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-4d05ffd7-0505-43e1-a068-0207c56b7665', 'example': False, 'tool_calls': [], 'invalid_tool_calls': []}]}})\n", + "StreamPart(event='end', data=None)\n" + ] + } + ], + "source": [ + "input = {\"messages\": [{\"role\": \"user\", \"content\": \"and you?\"}]}\n", + "async for event in client.runs.stream(thread['thread_id'], default_assistant['assistant_id'], input=input, stream_mode='updates'):\n", + " print(event)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4c26df68-c447-4a88-bc94-59df42b117b5", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.1" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/cloud_examples/stream_messages.ipynb b/examples/cloud_examples/stream_messages.ipynb new file mode 100644 index 000000000..60439088d --- /dev/null +++ b/examples/cloud_examples/stream_messages.ipynb @@ -0,0 +1,1402 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "51466c8d-8ce4-4b3d-be4e-18fdbeda5f53", + "metadata": {}, + "source": [ + "# How to stream messages from your graph\n", + "\n", + "There are multiple different streaming modes.\n", + "\n", + "- `values`: This streaming mode streams back values of the graph. This is the **full state of the graph** after each node is called.\n", + "- `updates`: This streaming mode streams back updates to the graph. This is the **update to the state of the graph** after each node is called.\n", + "- `messages`: This streaming mode streams back messages - both complete messages (at the end of a node) as well as **tokens** for any messages generated inside a node. This mode is primarily meant for powering chat applications.\n", + "\n", + "\n", + "This notebook covers `streaming_mode=\"messages\"`.\n", + "\n", + "In order to use this mode, the state of the graph you are interacting with MUST have a messages key that is a list of messages.\n", + "Eg, the state should look something like:\n", + "\n", + "```python\n", + "from typing import TypedDict, Annotated\n", + "from langgraph.graph import add_messages\n", + "from langchain_core.messages import AnyMessage\n", + "\n", + "class State(TypedDict):\n", + " messages: Annotated[list[AnyMessage], add_messages]\n", + "```\n", + "\n", + "OR it should be an instance or subclass of `from langgraph.graph import MessageState` (`MessageState` is just a helper type hint equivalent to the above).\n", + "\n", + "With `stream_mode=\"messages\"` two things will be streamed back:\n", + "\n", + "- It outputs messages produced by any chat model called inside (unless tagged in a special way)\n", + "- It outputs messages returned from nodes (to allow for nodes to return `ToolMessages` and the like" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "521d975b-e94b-4c37-bfa1-82d969e2a4dc", + "metadata": {}, + "outputs": [], + "source": [ + "from langgraph_sdk import get_client" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "27a1392b-86c3-464e-99a8-90ffc965f3ec", + "metadata": {}, + "outputs": [], + "source": [ + "client = get_client()" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "714e9f92-86b4-4cd8-9d68-cfc45d56ed2c", + "metadata": {}, + "outputs": [], + "source": [ + "assistant = await client.assistants.create(graph_id=\"agent\", config={\"configurable\": {\"model_name\": \"openai\"}})" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "4947e9bc-111f-4991-8c41-1041da9bf0ba", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'assistant_id': 'e6de0bea-86b1-4902-a20b-1f60caf24ef9',\n", + " 'graph_id': 'agent',\n", + " 'config': {'configurable': {'model_name': 'openai'}},\n", + " 'created_at': '2024-05-18T19:58:45.145734+00:00',\n", + " 'updated_at': '2024-05-18T19:58:45.145734+00:00',\n", + " 'metadata': {}}" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "assistant" + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "id": "56aa5159-5583-4134-9210-709b969bda6f", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'thread_id': 'd7d3507f-8242-4705-ae6a-2bf45aefe38c',\n", + " 'created_at': '2024-05-18T20:14:05.504749+00:00',\n", + " 'updated_at': '2024-05-18T20:14:05.504749+00:00',\n", + " 'metadata': {}}" + ] + }, + "execution_count": 43, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "thread = await client.threads.create()\n", + "thread" + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "id": "147c3f98-f889-4f05-a090-6b31f2a0b291", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[]" + ] + }, + "execution_count": 44, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "runs = await client.runs.list(thread['thread_id'])\n", + "runs" + ] + }, + { + "cell_type": "code", + "execution_count": 46, + "id": "040795c6-5d9f-4729-9132-f3b0f94d9e94", + "metadata": {}, + "outputs": [], + "source": [ + "# Helper function for formatting messages\n", + "\n", + "def format_tool_calls(tool_calls):\n", + " if tool_calls:\n", + " formatted_calls = []\n", + " for call in tool_calls:\n", + " formatted_calls.append(f\"Tool Call ID: {call['id']}, Function: {call['name']}, Arguments: {call['args']}\")\n", + " return \"\\n\".join(formatted_calls)\n", + " return \"No tool calls\"" + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "id": "7da70e20-1a4e-4df2-b996-1927f474c835", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Metadata: Run ID - c64b3e27-c7a4-4851-9618-07641fa24296\n", + "--------------------------------------------------\n", + "Human: whats the weather in sf\n", + "--------------------------------------------------\n", + "Invalid Tool Calls:\n", + "Tool Call ID: call_8XskpDf4mjpQOuIL8F9IzQR8, Function: tavily_search_results_json, Arguments: \n", + "--------------------------------------------------\n", + "Tool Calls:\n", + "Tool Call ID: call_8XskpDf4mjpQOuIL8F9IzQR8, Function: tavily_search_results_json, Arguments: {}\n", + "--------------------------------------------------\n", + "Tool Calls:\n", + "Tool Call ID: call_8XskpDf4mjpQOuIL8F9IzQR8, Function: tavily_search_results_json, Arguments: {}\n", + "--------------------------------------------------\n", + "Tool Calls:\n", + "Tool Call ID: call_8XskpDf4mjpQOuIL8F9IzQR8, Function: tavily_search_results_json, Arguments: {'query': ''}\n", + "--------------------------------------------------\n", + "Tool Calls:\n", + "Tool Call ID: call_8XskpDf4mjpQOuIL8F9IzQR8, Function: tavily_search_results_json, Arguments: {'query': 'current'}\n", + "--------------------------------------------------\n", + "Tool Calls:\n", + "Tool Call ID: call_8XskpDf4mjpQOuIL8F9IzQR8, Function: tavily_search_results_json, Arguments: {'query': 'current weather'}\n", + "--------------------------------------------------\n", + "Tool Calls:\n", + "Tool Call ID: call_8XskpDf4mjpQOuIL8F9IzQR8, Function: tavily_search_results_json, Arguments: {'query': 'current weather in'}\n", + "--------------------------------------------------\n", + "Tool Calls:\n", + "Tool Call ID: call_8XskpDf4mjpQOuIL8F9IzQR8, Function: tavily_search_results_json, Arguments: {'query': 'current weather in San'}\n", + "--------------------------------------------------\n", + "Tool Calls:\n", + "Tool Call ID: call_8XskpDf4mjpQOuIL8F9IzQR8, Function: tavily_search_results_json, Arguments: {'query': 'current weather in San Francisco'}\n", + "--------------------------------------------------\n", + "Tool Calls:\n", + "Tool Call ID: call_8XskpDf4mjpQOuIL8F9IzQR8, Function: tavily_search_results_json, Arguments: {'query': 'current weather in San Francisco'}\n", + "--------------------------------------------------\n", + "Tool Calls:\n", + "Tool Call ID: call_8XskpDf4mjpQOuIL8F9IzQR8, Function: tavily_search_results_json, Arguments: {'query': 'current weather in San Francisco'}\n", + "Response Metadata: Finish Reason - tool_calls\n", + "--------------------------------------------------\n", + "AI: whats the weather in sf\n", + "Tool Calls:\n", + "Tool Call ID: call_8XskpDf4mjpQOuIL8F9IzQR8, Function: tavily_search_results_json, Arguments: {'query': 'current weather in San Francisco'}\n", + "Response Metadata: Finish Reason - tool_calls\n", + "--------------------------------------------------\n", + "AI: [{\"url\": \"https://www.weatherapi.com/\", \"content\": \"{'location': {'name': 'San Francisco', 'region': 'California', 'country': 'United States of America', 'lat': 37.78, 'lon': -122.42, 'tz_id': 'America/Los_Angeles', 'localtime_epoch': 1716063227, 'localtime': '2024-05-18 13:13'}, 'current': {'last_updated_epoch': 1716062400, 'last_updated': '2024-05-18 13:00', 'temp_c': 17.2, 'temp_f': 63.0, 'is_day': 1, 'condition': {'text': 'Partly cloudy', 'icon': '//cdn.weatherapi.com/weather/64x64/day/116.png', 'code': 1003}, 'wind_mph': 2.2, 'wind_kph': 3.6, 'wind_degree': 237, 'wind_dir': 'WSW', 'pressure_mb': 1015.0, 'pressure_in': 29.98, 'precip_mm': 0.0, 'precip_in': 0.0, 'humidity': 65, 'cloud': 50, 'feelslike_c': 17.2, 'feelslike_f': 63.0, 'vis_km': 16.0, 'vis_miles': 9.0, 'uv': 5.0, 'gust_mph': 13.8, 'gust_kph': 22.3}}\"}]\n", + "--------------------------------------------------\n", + "--------------------------------------------------\n", + "AI: The\n", + "--------------------------------------------------\n", + "AI: The current\n", + "--------------------------------------------------\n", + "AI: The current weather\n", + "--------------------------------------------------\n", + "AI: The current weather in\n", + "--------------------------------------------------\n", + "AI: The current weather in San\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "-\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:**\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** \n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "-\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:**\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Part\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "-\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:**\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** \n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** 2\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** 2.\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** 2.2\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** 2.2 mph\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** 2.2 mph (\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** 2.2 mph (3\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** 2.2 mph (3.\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** 2.2 mph (3.6\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** 2.2 mph (3.6 k\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** 2.2 mph (3.6 kph\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** 2.2 mph (3.6 kph)\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** 2.2 mph (3.6 kph) from\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** 2.2 mph (3.6 kph) from the\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** 2.2 mph (3.6 kph) from the W\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** 2.2 mph (3.6 kph) from the WSW\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** 2.2 mph (3.6 kph) from the WSW\n", + "\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** 2.2 mph (3.6 kph) from the WSW\n", + "-\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** 2.2 mph (3.6 kph) from the WSW\n", + "- **\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** 2.2 mph (3.6 kph) from the WSW\n", + "- **Humidity\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** 2.2 mph (3.6 kph) from the WSW\n", + "- **Humidity:**\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** 2.2 mph (3.6 kph) from the WSW\n", + "- **Humidity:** \n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** 2.2 mph (3.6 kph) from the WSW\n", + "- **Humidity:** 65\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** 2.2 mph (3.6 kph) from the WSW\n", + "- **Humidity:** 65%\n", + "\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** 2.2 mph (3.6 kph) from the WSW\n", + "- **Humidity:** 65%\n", + "-\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** 2.2 mph (3.6 kph) from the WSW\n", + "- **Humidity:** 65%\n", + "- **\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** 2.2 mph (3.6 kph) from the WSW\n", + "- **Humidity:** 65%\n", + "- **Visibility\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** 2.2 mph (3.6 kph) from the WSW\n", + "- **Humidity:** 65%\n", + "- **Visibility:**\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** 2.2 mph (3.6 kph) from the WSW\n", + "- **Humidity:** 65%\n", + "- **Visibility:** \n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** 2.2 mph (3.6 kph) from the WSW\n", + "- **Humidity:** 65%\n", + "- **Visibility:** 16\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** 2.2 mph (3.6 kph) from the WSW\n", + "- **Humidity:** 65%\n", + "- **Visibility:** 16 km\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** 2.2 mph (3.6 kph) from the WSW\n", + "- **Humidity:** 65%\n", + "- **Visibility:** 16 km (\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** 2.2 mph (3.6 kph) from the WSW\n", + "- **Humidity:** 65%\n", + "- **Visibility:** 16 km (9\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** 2.2 mph (3.6 kph) from the WSW\n", + "- **Humidity:** 65%\n", + "- **Visibility:** 16 km (9 miles\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** 2.2 mph (3.6 kph) from the WSW\n", + "- **Humidity:** 65%\n", + "- **Visibility:** 16 km (9 miles)\n", + "\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** 2.2 mph (3.6 kph) from the WSW\n", + "- **Humidity:** 65%\n", + "- **Visibility:** 16 km (9 miles)\n", + "-\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** 2.2 mph (3.6 kph) from the WSW\n", + "- **Humidity:** 65%\n", + "- **Visibility:** 16 km (9 miles)\n", + "- **\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** 2.2 mph (3.6 kph) from the WSW\n", + "- **Humidity:** 65%\n", + "- **Visibility:** 16 km (9 miles)\n", + "- **Pressure\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** 2.2 mph (3.6 kph) from the WSW\n", + "- **Humidity:** 65%\n", + "- **Visibility:** 16 km (9 miles)\n", + "- **Pressure:**\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** 2.2 mph (3.6 kph) from the WSW\n", + "- **Humidity:** 65%\n", + "- **Visibility:** 16 km (9 miles)\n", + "- **Pressure:** \n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** 2.2 mph (3.6 kph) from the WSW\n", + "- **Humidity:** 65%\n", + "- **Visibility:** 16 km (9 miles)\n", + "- **Pressure:** 101\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** 2.2 mph (3.6 kph) from the WSW\n", + "- **Humidity:** 65%\n", + "- **Visibility:** 16 km (9 miles)\n", + "- **Pressure:** 1015\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** 2.2 mph (3.6 kph) from the WSW\n", + "- **Humidity:** 65%\n", + "- **Visibility:** 16 km (9 miles)\n", + "- **Pressure:** 1015.\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** 2.2 mph (3.6 kph) from the WSW\n", + "- **Humidity:** 65%\n", + "- **Visibility:** 16 km (9 miles)\n", + "- **Pressure:** 1015.0\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** 2.2 mph (3.6 kph) from the WSW\n", + "- **Humidity:** 65%\n", + "- **Visibility:** 16 km (9 miles)\n", + "- **Pressure:** 1015.0 mb\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** 2.2 mph (3.6 kph) from the WSW\n", + "- **Humidity:** 65%\n", + "- **Visibility:** 16 km (9 miles)\n", + "- **Pressure:** 1015.0 mb (\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** 2.2 mph (3.6 kph) from the WSW\n", + "- **Humidity:** 65%\n", + "- **Visibility:** 16 km (9 miles)\n", + "- **Pressure:** 1015.0 mb (29\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** 2.2 mph (3.6 kph) from the WSW\n", + "- **Humidity:** 65%\n", + "- **Visibility:** 16 km (9 miles)\n", + "- **Pressure:** 1015.0 mb (29.\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** 2.2 mph (3.6 kph) from the WSW\n", + "- **Humidity:** 65%\n", + "- **Visibility:** 16 km (9 miles)\n", + "- **Pressure:** 1015.0 mb (29.98\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** 2.2 mph (3.6 kph) from the WSW\n", + "- **Humidity:** 65%\n", + "- **Visibility:** 16 km (9 miles)\n", + "- **Pressure:** 1015.0 mb (29.98 in\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** 2.2 mph (3.6 kph) from the WSW\n", + "- **Humidity:** 65%\n", + "- **Visibility:** 16 km (9 miles)\n", + "- **Pressure:** 1015.0 mb (29.98 in)\n", + "\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** 2.2 mph (3.6 kph) from the WSW\n", + "- **Humidity:** 65%\n", + "- **Visibility:** 16 km (9 miles)\n", + "- **Pressure:** 1015.0 mb (29.98 in)\n", + "-\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** 2.2 mph (3.6 kph) from the WSW\n", + "- **Humidity:** 65%\n", + "- **Visibility:** 16 km (9 miles)\n", + "- **Pressure:** 1015.0 mb (29.98 in)\n", + "- **\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** 2.2 mph (3.6 kph) from the WSW\n", + "- **Humidity:** 65%\n", + "- **Visibility:** 16 km (9 miles)\n", + "- **Pressure:** 1015.0 mb (29.98 in)\n", + "- **UV\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** 2.2 mph (3.6 kph) from the WSW\n", + "- **Humidity:** 65%\n", + "- **Visibility:** 16 km (9 miles)\n", + "- **Pressure:** 1015.0 mb (29.98 in)\n", + "- **UV Index\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** 2.2 mph (3.6 kph) from the WSW\n", + "- **Humidity:** 65%\n", + "- **Visibility:** 16 km (9 miles)\n", + "- **Pressure:** 1015.0 mb (29.98 in)\n", + "- **UV Index:**\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** 2.2 mph (3.6 kph) from the WSW\n", + "- **Humidity:** 65%\n", + "- **Visibility:** 16 km (9 miles)\n", + "- **Pressure:** 1015.0 mb (29.98 in)\n", + "- **UV Index:** \n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** 2.2 mph (3.6 kph) from the WSW\n", + "- **Humidity:** 65%\n", + "- **Visibility:** 16 km (9 miles)\n", + "- **Pressure:** 1015.0 mb (29.98 in)\n", + "- **UV Index:** 5\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** 2.2 mph (3.6 kph) from the WSW\n", + "- **Humidity:** 65%\n", + "- **Visibility:** 16 km (9 miles)\n", + "- **Pressure:** 1015.0 mb (29.98 in)\n", + "- **UV Index:** 5\n", + "\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** 2.2 mph (3.6 kph) from the WSW\n", + "- **Humidity:** 65%\n", + "- **Visibility:** 16 km (9 miles)\n", + "- **Pressure:** 1015.0 mb (29.98 in)\n", + "- **UV Index:** 5\n", + "-\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** 2.2 mph (3.6 kph) from the WSW\n", + "- **Humidity:** 65%\n", + "- **Visibility:** 16 km (9 miles)\n", + "- **Pressure:** 1015.0 mb (29.98 in)\n", + "- **UV Index:** 5\n", + "- **\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** 2.2 mph (3.6 kph) from the WSW\n", + "- **Humidity:** 65%\n", + "- **Visibility:** 16 km (9 miles)\n", + "- **Pressure:** 1015.0 mb (29.98 in)\n", + "- **UV Index:** 5\n", + "- **G\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** 2.2 mph (3.6 kph) from the WSW\n", + "- **Humidity:** 65%\n", + "- **Visibility:** 16 km (9 miles)\n", + "- **Pressure:** 1015.0 mb (29.98 in)\n", + "- **UV Index:** 5\n", + "- **Gust\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** 2.2 mph (3.6 kph) from the WSW\n", + "- **Humidity:** 65%\n", + "- **Visibility:** 16 km (9 miles)\n", + "- **Pressure:** 1015.0 mb (29.98 in)\n", + "- **UV Index:** 5\n", + "- **Gusts\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** 2.2 mph (3.6 kph) from the WSW\n", + "- **Humidity:** 65%\n", + "- **Visibility:** 16 km (9 miles)\n", + "- **Pressure:** 1015.0 mb (29.98 in)\n", + "- **UV Index:** 5\n", + "- **Gusts:**\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** 2.2 mph (3.6 kph) from the WSW\n", + "- **Humidity:** 65%\n", + "- **Visibility:** 16 km (9 miles)\n", + "- **Pressure:** 1015.0 mb (29.98 in)\n", + "- **UV Index:** 5\n", + "- **Gusts:** Up\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** 2.2 mph (3.6 kph) from the WSW\n", + "- **Humidity:** 65%\n", + "- **Visibility:** 16 km (9 miles)\n", + "- **Pressure:** 1015.0 mb (29.98 in)\n", + "- **UV Index:** 5\n", + "- **Gusts:** Up to\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** 2.2 mph (3.6 kph) from the WSW\n", + "- **Humidity:** 65%\n", + "- **Visibility:** 16 km (9 miles)\n", + "- **Pressure:** 1015.0 mb (29.98 in)\n", + "- **UV Index:** 5\n", + "- **Gusts:** Up to \n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** 2.2 mph (3.6 kph) from the WSW\n", + "- **Humidity:** 65%\n", + "- **Visibility:** 16 km (9 miles)\n", + "- **Pressure:** 1015.0 mb (29.98 in)\n", + "- **UV Index:** 5\n", + "- **Gusts:** Up to 13\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** 2.2 mph (3.6 kph) from the WSW\n", + "- **Humidity:** 65%\n", + "- **Visibility:** 16 km (9 miles)\n", + "- **Pressure:** 1015.0 mb (29.98 in)\n", + "- **UV Index:** 5\n", + "- **Gusts:** Up to 13.\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** 2.2 mph (3.6 kph) from the WSW\n", + "- **Humidity:** 65%\n", + "- **Visibility:** 16 km (9 miles)\n", + "- **Pressure:** 1015.0 mb (29.98 in)\n", + "- **UV Index:** 5\n", + "- **Gusts:** Up to 13.8\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** 2.2 mph (3.6 kph) from the WSW\n", + "- **Humidity:** 65%\n", + "- **Visibility:** 16 km (9 miles)\n", + "- **Pressure:** 1015.0 mb (29.98 in)\n", + "- **UV Index:** 5\n", + "- **Gusts:** Up to 13.8 mph\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** 2.2 mph (3.6 kph) from the WSW\n", + "- **Humidity:** 65%\n", + "- **Visibility:** 16 km (9 miles)\n", + "- **Pressure:** 1015.0 mb (29.98 in)\n", + "- **UV Index:** 5\n", + "- **Gusts:** Up to 13.8 mph (\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** 2.2 mph (3.6 kph) from the WSW\n", + "- **Humidity:** 65%\n", + "- **Visibility:** 16 km (9 miles)\n", + "- **Pressure:** 1015.0 mb (29.98 in)\n", + "- **UV Index:** 5\n", + "- **Gusts:** Up to 13.8 mph (22\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** 2.2 mph (3.6 kph) from the WSW\n", + "- **Humidity:** 65%\n", + "- **Visibility:** 16 km (9 miles)\n", + "- **Pressure:** 1015.0 mb (29.98 in)\n", + "- **UV Index:** 5\n", + "- **Gusts:** Up to 13.8 mph (22.\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** 2.2 mph (3.6 kph) from the WSW\n", + "- **Humidity:** 65%\n", + "- **Visibility:** 16 km (9 miles)\n", + "- **Pressure:** 1015.0 mb (29.98 in)\n", + "- **UV Index:** 5\n", + "- **Gusts:** Up to 13.8 mph (22.3\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** 2.2 mph (3.6 kph) from the WSW\n", + "- **Humidity:** 65%\n", + "- **Visibility:** 16 km (9 miles)\n", + "- **Pressure:** 1015.0 mb (29.98 in)\n", + "- **UV Index:** 5\n", + "- **Gusts:** Up to 13.8 mph (22.3 k\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** 2.2 mph (3.6 kph) from the WSW\n", + "- **Humidity:** 65%\n", + "- **Visibility:** 16 km (9 miles)\n", + "- **Pressure:** 1015.0 mb (29.98 in)\n", + "- **UV Index:** 5\n", + "- **Gusts:** Up to 13.8 mph (22.3 kph\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** 2.2 mph (3.6 kph) from the WSW\n", + "- **Humidity:** 65%\n", + "- **Visibility:** 16 km (9 miles)\n", + "- **Pressure:** 1015.0 mb (29.98 in)\n", + "- **UV Index:** 5\n", + "- **Gusts:** Up to 13.8 mph (22.3 kph)\n", + "\n", + "\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** 2.2 mph (3.6 kph) from the WSW\n", + "- **Humidity:** 65%\n", + "- **Visibility:** 16 km (9 miles)\n", + "- **Pressure:** 1015.0 mb (29.98 in)\n", + "- **UV Index:** 5\n", + "- **Gusts:** Up to 13.8 mph (22.3 kph)\n", + "\n", + "![\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** 2.2 mph (3.6 kph) from the WSW\n", + "- **Humidity:** 65%\n", + "- **Visibility:** 16 km (9 miles)\n", + "- **Pressure:** 1015.0 mb (29.98 in)\n", + "- **UV Index:** 5\n", + "- **Gusts:** Up to 13.8 mph (22.3 kph)\n", + "\n", + "![Part\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** 2.2 mph (3.6 kph) from the WSW\n", + "- **Humidity:** 65%\n", + "- **Visibility:** 16 km (9 miles)\n", + "- **Pressure:** 1015.0 mb (29.98 in)\n", + "- **UV Index:** 5\n", + "- **Gusts:** Up to 13.8 mph (22.3 kph)\n", + "\n", + "![Partly\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** 2.2 mph (3.6 kph) from the WSW\n", + "- **Humidity:** 65%\n", + "- **Visibility:** 16 km (9 miles)\n", + "- **Pressure:** 1015.0 mb (29.98 in)\n", + "- **UV Index:** 5\n", + "- **Gusts:** Up to 13.8 mph (22.3 kph)\n", + "\n", + "![Partly cloudy\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** 2.2 mph (3.6 kph) from the WSW\n", + "- **Humidity:** 65%\n", + "- **Visibility:** 16 km (9 miles)\n", + "- **Pressure:** 1015.0 mb (29.98 in)\n", + "- **UV Index:** 5\n", + "- **Gusts:** Up to 13.8 mph (22.3 kph)\n", + "\n", + "![Partly cloudy](\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** 2.2 mph (3.6 kph) from the WSW\n", + "- **Humidity:** 65%\n", + "- **Visibility:** 16 km (9 miles)\n", + "- **Pressure:** 1015.0 mb (29.98 in)\n", + "- **UV Index:** 5\n", + "- **Gusts:** Up to 13.8 mph (22.3 kph)\n", + "\n", + "![Partly cloudy](//\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** 2.2 mph (3.6 kph) from the WSW\n", + "- **Humidity:** 65%\n", + "- **Visibility:** 16 km (9 miles)\n", + "- **Pressure:** 1015.0 mb (29.98 in)\n", + "- **UV Index:** 5\n", + "- **Gusts:** Up to 13.8 mph (22.3 kph)\n", + "\n", + "![Partly cloudy](//cdn\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** 2.2 mph (3.6 kph) from the WSW\n", + "- **Humidity:** 65%\n", + "- **Visibility:** 16 km (9 miles)\n", + "- **Pressure:** 1015.0 mb (29.98 in)\n", + "- **UV Index:** 5\n", + "- **Gusts:** Up to 13.8 mph (22.3 kph)\n", + "\n", + "![Partly cloudy](//cdn.weather\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** 2.2 mph (3.6 kph) from the WSW\n", + "- **Humidity:** 65%\n", + "- **Visibility:** 16 km (9 miles)\n", + "- **Pressure:** 1015.0 mb (29.98 in)\n", + "- **UV Index:** 5\n", + "- **Gusts:** Up to 13.8 mph (22.3 kph)\n", + "\n", + "![Partly cloudy](//cdn.weatherapi\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** 2.2 mph (3.6 kph) from the WSW\n", + "- **Humidity:** 65%\n", + "- **Visibility:** 16 km (9 miles)\n", + "- **Pressure:** 1015.0 mb (29.98 in)\n", + "- **UV Index:** 5\n", + "- **Gusts:** Up to 13.8 mph (22.3 kph)\n", + "\n", + "![Partly cloudy](//cdn.weatherapi.com\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** 2.2 mph (3.6 kph) from the WSW\n", + "- **Humidity:** 65%\n", + "- **Visibility:** 16 km (9 miles)\n", + "- **Pressure:** 1015.0 mb (29.98 in)\n", + "- **UV Index:** 5\n", + "- **Gusts:** Up to 13.8 mph (22.3 kph)\n", + "\n", + "![Partly cloudy](//cdn.weatherapi.com/weather\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** 2.2 mph (3.6 kph) from the WSW\n", + "- **Humidity:** 65%\n", + "- **Visibility:** 16 km (9 miles)\n", + "- **Pressure:** 1015.0 mb (29.98 in)\n", + "- **UV Index:** 5\n", + "- **Gusts:** Up to 13.8 mph (22.3 kph)\n", + "\n", + "![Partly cloudy](//cdn.weatherapi.com/weather/\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** 2.2 mph (3.6 kph) from the WSW\n", + "- **Humidity:** 65%\n", + "- **Visibility:** 16 km (9 miles)\n", + "- **Pressure:** 1015.0 mb (29.98 in)\n", + "- **UV Index:** 5\n", + "- **Gusts:** Up to 13.8 mph (22.3 kph)\n", + "\n", + "![Partly cloudy](//cdn.weatherapi.com/weather/64\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** 2.2 mph (3.6 kph) from the WSW\n", + "- **Humidity:** 65%\n", + "- **Visibility:** 16 km (9 miles)\n", + "- **Pressure:** 1015.0 mb (29.98 in)\n", + "- **UV Index:** 5\n", + "- **Gusts:** Up to 13.8 mph (22.3 kph)\n", + "\n", + "![Partly cloudy](//cdn.weatherapi.com/weather/64x\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** 2.2 mph (3.6 kph) from the WSW\n", + "- **Humidity:** 65%\n", + "- **Visibility:** 16 km (9 miles)\n", + "- **Pressure:** 1015.0 mb (29.98 in)\n", + "- **UV Index:** 5\n", + "- **Gusts:** Up to 13.8 mph (22.3 kph)\n", + "\n", + "![Partly cloudy](//cdn.weatherapi.com/weather/64x64\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** 2.2 mph (3.6 kph) from the WSW\n", + "- **Humidity:** 65%\n", + "- **Visibility:** 16 km (9 miles)\n", + "- **Pressure:** 1015.0 mb (29.98 in)\n", + "- **UV Index:** 5\n", + "- **Gusts:** Up to 13.8 mph (22.3 kph)\n", + "\n", + "![Partly cloudy](//cdn.weatherapi.com/weather/64x64/day\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** 2.2 mph (3.6 kph) from the WSW\n", + "- **Humidity:** 65%\n", + "- **Visibility:** 16 km (9 miles)\n", + "- **Pressure:** 1015.0 mb (29.98 in)\n", + "- **UV Index:** 5\n", + "- **Gusts:** Up to 13.8 mph (22.3 kph)\n", + "\n", + "![Partly cloudy](//cdn.weatherapi.com/weather/64x64/day/\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** 2.2 mph (3.6 kph) from the WSW\n", + "- **Humidity:** 65%\n", + "- **Visibility:** 16 km (9 miles)\n", + "- **Pressure:** 1015.0 mb (29.98 in)\n", + "- **UV Index:** 5\n", + "- **Gusts:** Up to 13.8 mph (22.3 kph)\n", + "\n", + "![Partly cloudy](//cdn.weatherapi.com/weather/64x64/day/116\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** 2.2 mph (3.6 kph) from the WSW\n", + "- **Humidity:** 65%\n", + "- **Visibility:** 16 km (9 miles)\n", + "- **Pressure:** 1015.0 mb (29.98 in)\n", + "- **UV Index:** 5\n", + "- **Gusts:** Up to 13.8 mph (22.3 kph)\n", + "\n", + "![Partly cloudy](//cdn.weatherapi.com/weather/64x64/day/116.png\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** 2.2 mph (3.6 kph) from the WSW\n", + "- **Humidity:** 65%\n", + "- **Visibility:** 16 km (9 miles)\n", + "- **Pressure:** 1015.0 mb (29.98 in)\n", + "- **UV Index:** 5\n", + "- **Gusts:** Up to 13.8 mph (22.3 kph)\n", + "\n", + "![Partly cloudy](//cdn.weatherapi.com/weather/64x64/day/116.png)\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** 2.2 mph (3.6 kph) from the WSW\n", + "- **Humidity:** 65%\n", + "- **Visibility:** 16 km (9 miles)\n", + "- **Pressure:** 1015.0 mb (29.98 in)\n", + "- **UV Index:** 5\n", + "- **Gusts:** Up to 13.8 mph (22.3 kph)\n", + "\n", + "![Partly cloudy](//cdn.weatherapi.com/weather/64x64/day/116.png)\n", + "Response Metadata: Finish Reason - stop\n", + "--------------------------------------------------\n", + "AI: The current weather in San Francisco is as follows:\n", + "\n", + "- **Temperature:** 17.2°C (63.0°F)\n", + "- **Condition:** Partly cloudy\n", + "- **Wind:** 2.2 mph (3.6 kph) from the WSW\n", + "- **Humidity:** 65%\n", + "- **Visibility:** 16 km (9 miles)\n", + "- **Pressure:** 1015.0 mb (29.98 in)\n", + "- **UV Index:** 5\n", + "- **Gusts:** Up to 13.8 mph (22.3 kph)\n", + "\n", + "![Partly cloudy](//cdn.weatherapi.com/weather/64x64/day/116.png)\n", + "Response Metadata: Finish Reason - stop\n", + "--------------------------------------------------\n", + "--------------------------------------------------\n" + ] + } + ], + "source": [ + "input = {\"messages\": [{\"role\": \"user\", \"content\": \"whats the weather in sf\"}]}\n", + "\n", + "async for event in client.runs.stream(thread['thread_id'], assistant['assistant_id'], input=input, stream_mode='messages'):\n", + " if event.event == 'metadata':\n", + " print(f\"Metadata: Run ID - {event.data['run_id']}\")\n", + " elif event.event == 'data':\n", + " for data_item in event.data:\n", + " if 'role' in data_item and data_item['role'] == 'user':\n", + " print(f\"Human: {data_item['content']}\")\n", + " else:\n", + " tool_calls = data_item.get('tool_calls', [])\n", + " invalid_tool_calls = data_item.get('invalid_tool_calls', [])\n", + " content = data_item.get('content', \"\")\n", + " response_metadata = data_item.get('response_metadata', {})\n", + "\n", + " if content:\n", + " print(f\"AI: {content}\")\n", + " \n", + " if tool_calls:\n", + " print(\"Tool Calls:\")\n", + " print(format_tool_calls(tool_calls))\n", + " \n", + " if invalid_tool_calls:\n", + " print(\"Invalid Tool Calls:\")\n", + " print(format_tool_calls(invalid_tool_calls))\n", + "\n", + " if response_metadata:\n", + " finish_reason = response_metadata.get('finish_reason', 'N/A')\n", + " print(f\"Response Metadata: Finish Reason - {finish_reason}\")\n", + " print(\"-\" * 50)\n", + " " + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.1" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/cloud_examples/stream_updates.ipynb b/examples/cloud_examples/stream_updates.ipynb new file mode 100644 index 000000000..877194b53 --- /dev/null +++ b/examples/cloud_examples/stream_updates.ipynb @@ -0,0 +1,219 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "51466c8d-8ce4-4b3d-be4e-18fdbeda5f53", + "metadata": {}, + "source": [ + "# How to stream updates from your graph\n", + "\n", + "There are multiple different streaming modes.\n", + "\n", + "- `values`: This streaming mode streams back values of the graph. This is the **full state of the graph** after each node is called.\n", + "- `updates`: This streaming mode streams back updates to the graph. This is the **update to the state of the graph** after each node is called.\n", + "- `messages`: This streaming mode streams back messages - both complete messages (at the end of a node) as well as **tokens** for any messages generated inside a node. This mode is primarily meant for powering chat applications.\n", + "\n", + "\n", + "This notebook covers `streaming_mode=\"updates\"`." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "521d975b-e94b-4c37-bfa1-82d969e2a4dc", + "metadata": {}, + "outputs": [], + "source": [ + "from langgraph_sdk import get_client" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "27a1392b-86c3-464e-99a8-90ffc965f3ec", + "metadata": {}, + "outputs": [], + "source": [ + "client = get_client()" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "4947e9bc-111f-4991-8c41-1041da9bf0ba", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[{'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca',\n", + " 'graph_id': 'agent',\n", + " 'config': {},\n", + " 'created_at': '2024-05-18T00:19:39.688822+00:00',\n", + " 'updated_at': '2024-05-18T00:19:39.688822+00:00',\n", + " 'metadata': {'created_by': 'system'}}]" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# There should always be a default assistant with no configuration\n", + "assistants = await client.assistants.search()\n", + "assistants = [a for a in assistants if not a['config']]\n", + "assistants" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "230c0464-a6e5-420f-9e38-ca514e5634ce", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca',\n", + " 'graph_id': 'agent',\n", + " 'config': {},\n", + " 'created_at': '2024-05-18T00:19:39.688822+00:00',\n", + " 'updated_at': '2024-05-18T00:19:39.688822+00:00',\n", + " 'metadata': {'created_by': 'system'}}" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "assistant = assistants[0]\n", + "assistant" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "56aa5159-5583-4134-9210-709b969bda6f", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'thread_id': '1eee9bd4-ec61-4300-a38a-2e13b8925d39',\n", + " 'created_at': '2024-05-18T19:57:36.509105+00:00',\n", + " 'updated_at': '2024-05-18T19:57:36.509105+00:00',\n", + " 'metadata': {}}" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "thread = await client.threads.create()\n", + "thread" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "147c3f98-f889-4f05-a090-6b31f2a0b291", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[]" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "runs = await client.runs.list(thread['thread_id'])\n", + "runs" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "7da70e20-1a4e-4df2-b996-1927f474c835", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Receiving new event of type: metadata...\n", + "{'run_id': 'cfc96c16-ed9a-44bd-b5bb-c30e3c0725f0'}\n", + "\n", + "\n", + "\n", + "Receiving new event of type: data...\n", + "{'agent': {'messages': [{'content': [{'id': 'toolu_0148tMmDK51iLQfG1yaNwRHM', 'input': {'query': 'weather in los angeles'}, 'name': 'tavily_search_results_json', 'type': 'tool_use'}], 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-1a9d32b0-7007-4a36-abde-8df812a0ed94', 'example': False, 'tool_calls': [{'name': 'tavily_search_results_json', 'args': {'query': 'weather in los angeles'}, 'id': 'toolu_0148tMmDK51iLQfG1yaNwRHM'}], 'invalid_tool_calls': []}]}}\n", + "\n", + "\n", + "\n", + "Receiving new event of type: data...\n", + "{'action': {'messages': [{'content': '[{\"url\": \"https://www.weatherapi.com/\", \"content\": \"{\\'location\\': {\\'name\\': \\'Los Angeles\\', \\'region\\': \\'California\\', \\'country\\': \\'United States of America\\', \\'lat\\': 34.05, \\'lon\\': -118.24, \\'tz_id\\': \\'America/Los_Angeles\\', \\'localtime_epoch\\': 1716062239, \\'localtime\\': \\'2024-05-18 12:57\\'}, \\'current\\': {\\'last_updated_epoch\\': 1716061500, \\'last_updated\\': \\'2024-05-18 12:45\\', \\'temp_c\\': 18.9, \\'temp_f\\': 66.0, \\'is_day\\': 1, \\'condition\\': {\\'text\\': \\'Overcast\\', \\'icon\\': \\'//cdn.weatherapi.com/weather/64x64/day/122.png\\', \\'code\\': 1009}, \\'wind_mph\\': 2.2, \\'wind_kph\\': 3.6, \\'wind_degree\\': 10, \\'wind_dir\\': \\'N\\', \\'pressure_mb\\': 1017.0, \\'pressure_in\\': 30.02, \\'precip_mm\\': 0.0, \\'precip_in\\': 0.0, \\'humidity\\': 65, \\'cloud\\': 100, \\'feelslike_c\\': 18.9, \\'feelslike_f\\': 66.0, \\'vis_km\\': 16.0, \\'vis_miles\\': 9.0, \\'uv\\': 6.0, \\'gust_mph\\': 7.5, \\'gust_kph\\': 12.0}}\"}]', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': 'tavily_search_results_json', 'id': 'a36e8cd1-0e96-4417-9c15-f10a945d2b42', 'tool_call_id': 'toolu_0148tMmDK51iLQfG1yaNwRHM'}]}}\n", + "\n", + "\n", + "\n", + "Receiving new event of type: data...\n", + "{'agent': {'messages': [{'content': 'The weather in Los Angeles is currently overcast with a temperature of around 66°F (18.9°C). There are light winds from the north at around 2-3 mph. The humidity is 65% and visibility is good at 9 miles. Overall, mild spring weather conditions in LA.', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-d5c1c2f0-b12d-41ce-990b-f36570e7483d', 'example': False, 'tool_calls': [], 'invalid_tool_calls': []}]}}\n", + "\n", + "\n", + "\n", + "Receiving new event of type: end...\n", + "None\n", + "\n", + "\n", + "\n" + ] + } + ], + "source": [ + "input = {\"messages\": [{\"role\": \"human\", \"content\": \"whats the weather in la\"}]}\n", + "async for chunk in client.runs.stream(thread['thread_id'], assistant['assistant_id'], input=input, stream_mode=\"updates\", ):\n", + " print(f\"Receiving new event of type: {chunk.event}...\")\n", + " print(chunk.data)\n", + " print(\"\\n\\n\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "53800469-354a-4739-8e77-b88044c772d5", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.1" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/cloud_examples/stream_values.ipynb b/examples/cloud_examples/stream_values.ipynb new file mode 100644 index 000000000..86493758c --- /dev/null +++ b/examples/cloud_examples/stream_values.ipynb @@ -0,0 +1,258 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "51466c8d-8ce4-4b3d-be4e-18fdbeda5f53", + "metadata": {}, + "source": [ + "# How to stream values from your graph\n", + "\n", + "There are multiple different streaming modes.\n", + "\n", + "- `values`: This streaming mode streams back values of the graph. This is the **full state of the graph** after each node is called.\n", + "- `updates`: This streaming mode streams back updates to the graph. This is the **update to the state of the graph** after each node is called.\n", + "- `messages`: This streaming mode streams back messages - both complete messages (at the end of a node) as well as **tokens** for any messages generated inside a node. This mode is primarily meant for powering chat applications.\n", + "\n", + "\n", + "This notebook covers `streaming_mode=\"values\"`." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "521d975b-e94b-4c37-bfa1-82d969e2a4dc", + "metadata": {}, + "outputs": [], + "source": [ + "from langgraph_sdk import get_client" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "27a1392b-86c3-464e-99a8-90ffc965f3ec", + "metadata": {}, + "outputs": [], + "source": [ + "client = get_client()" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "4947e9bc-111f-4991-8c41-1041da9bf0ba", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[{'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca',\n", + " 'graph_id': 'agent',\n", + " 'config': {},\n", + " 'created_at': '2024-05-18T00:19:39.688822+00:00',\n", + " 'updated_at': '2024-05-18T00:19:39.688822+00:00',\n", + " 'metadata': {'created_by': 'system'}}]" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# There should always be a default assistant with no configuration\n", + "assistants = await client.assistants.search()\n", + "assistants = [a for a in assistants if not a['config']]\n", + "assistants" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "230c0464-a6e5-420f-9e38-ca514e5634ce", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca',\n", + " 'graph_id': 'agent',\n", + " 'config': {},\n", + " 'created_at': '2024-05-18T00:19:39.688822+00:00',\n", + " 'updated_at': '2024-05-18T00:19:39.688822+00:00',\n", + " 'metadata': {'created_by': 'system'}}" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "assistant = assistants[0]\n", + "assistant" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "7da70e20-1a4e-4df2-b996-1927f474c835", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Receiving new event of type: metadata...\n", + "{'run_id': 'f08791ce-0a3d-44e0-836c-ff62cd2e2786'}\n", + "\n", + "\n", + "\n", + "Receiving new event of type: values...\n", + "{'messages': [{'role': 'human', 'content': 'whats the weather in la'}]}\n", + "\n", + "\n", + "\n", + "Receiving new event of type: values...\n", + "{'messages': [{'content': 'whats the weather in la', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': 'faa15565-8823-4aa1-87af-e21b40526fae', 'example': False}, {'content': [{'id': 'toolu_01E5mSaZWm5rWJnCqmt63v4g', 'input': {'query': 'weather in los angeles'}, 'name': 'tavily_search_results_json', 'type': 'tool_use'}], 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-3fe1db7a-6b8d-4d83-ba07-8657190ad811', 'example': False, 'tool_calls': [{'name': 'tavily_search_results_json', 'args': {'query': 'weather in los angeles'}, 'id': 'toolu_01E5mSaZWm5rWJnCqmt63v4g'}], 'invalid_tool_calls': []}]}\n", + "\n", + "\n", + "\n", + "Receiving new event of type: values...\n", + "{'messages': [{'content': 'whats the weather in la', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': 'faa15565-8823-4aa1-87af-e21b40526fae', 'example': False}, {'content': [{'id': 'toolu_01E5mSaZWm5rWJnCqmt63v4g', 'input': {'query': 'weather in los angeles'}, 'name': 'tavily_search_results_json', 'type': 'tool_use'}], 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-3fe1db7a-6b8d-4d83-ba07-8657190ad811', 'example': False, 'tool_calls': [{'name': 'tavily_search_results_json', 'args': {'query': 'weather in los angeles'}, 'id': 'toolu_01E5mSaZWm5rWJnCqmt63v4g'}], 'invalid_tool_calls': []}, {'content': '[{\"url\": \"https://www.weatherapi.com/\", \"content\": \"{\\'location\\': {\\'name\\': \\'Los Angeles\\', \\'region\\': \\'California\\', \\'country\\': \\'United States of America\\', \\'lat\\': 34.05, \\'lon\\': -118.24, \\'tz_id\\': \\'America/Los_Angeles\\', \\'localtime_epoch\\': 1716310320, \\'localtime\\': \\'2024-05-21 9:52\\'}, \\'current\\': {\\'last_updated_epoch\\': 1716309900, \\'last_updated\\': \\'2024-05-21 09:45\\', \\'temp_c\\': 16.7, \\'temp_f\\': 62.1, \\'is_day\\': 1, \\'condition\\': {\\'text\\': \\'Overcast\\', \\'icon\\': \\'//cdn.weatherapi.com/weather/64x64/day/122.png\\', \\'code\\': 1009}, \\'wind_mph\\': 8.1, \\'wind_kph\\': 13.0, \\'wind_degree\\': 250, \\'wind_dir\\': \\'WSW\\', \\'pressure_mb\\': 1015.0, \\'pressure_in\\': 29.97, \\'precip_mm\\': 0.0, \\'precip_in\\': 0.0, \\'humidity\\': 65, \\'cloud\\': 100, \\'feelslike_c\\': 16.7, \\'feelslike_f\\': 62.1, \\'vis_km\\': 16.0, \\'vis_miles\\': 9.0, \\'uv\\': 5.0, \\'gust_mph\\': 12.5, \\'gust_kph\\': 20.2}}\"}]', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': 'tavily_search_results_json', 'id': '0d5dab31-5ff8-4ae2-a560-bc4bcba7c9d7', 'tool_call_id': 'toolu_01E5mSaZWm5rWJnCqmt63v4g'}]}\n", + "\n", + "\n", + "\n", + "Receiving new event of type: values...\n", + "{'messages': [{'content': 'whats the weather in la', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': 'faa15565-8823-4aa1-87af-e21b40526fae', 'example': False}, {'content': [{'id': 'toolu_01E5mSaZWm5rWJnCqmt63v4g', 'input': {'query': 'weather in los angeles'}, 'name': 'tavily_search_results_json', 'type': 'tool_use'}], 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-3fe1db7a-6b8d-4d83-ba07-8657190ad811', 'example': False, 'tool_calls': [{'name': 'tavily_search_results_json', 'args': {'query': 'weather in los angeles'}, 'id': 'toolu_01E5mSaZWm5rWJnCqmt63v4g'}], 'invalid_tool_calls': []}, {'content': '[{\"url\": \"https://www.weatherapi.com/\", \"content\": \"{\\'location\\': {\\'name\\': \\'Los Angeles\\', \\'region\\': \\'California\\', \\'country\\': \\'United States of America\\', \\'lat\\': 34.05, \\'lon\\': -118.24, \\'tz_id\\': \\'America/Los_Angeles\\', \\'localtime_epoch\\': 1716310320, \\'localtime\\': \\'2024-05-21 9:52\\'}, \\'current\\': {\\'last_updated_epoch\\': 1716309900, \\'last_updated\\': \\'2024-05-21 09:45\\', \\'temp_c\\': 16.7, \\'temp_f\\': 62.1, \\'is_day\\': 1, \\'condition\\': {\\'text\\': \\'Overcast\\', \\'icon\\': \\'//cdn.weatherapi.com/weather/64x64/day/122.png\\', \\'code\\': 1009}, \\'wind_mph\\': 8.1, \\'wind_kph\\': 13.0, \\'wind_degree\\': 250, \\'wind_dir\\': \\'WSW\\', \\'pressure_mb\\': 1015.0, \\'pressure_in\\': 29.97, \\'precip_mm\\': 0.0, \\'precip_in\\': 0.0, \\'humidity\\': 65, \\'cloud\\': 100, \\'feelslike_c\\': 16.7, \\'feelslike_f\\': 62.1, \\'vis_km\\': 16.0, \\'vis_miles\\': 9.0, \\'uv\\': 5.0, \\'gust_mph\\': 12.5, \\'gust_kph\\': 20.2}}\"}]', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': 'tavily_search_results_json', 'id': '0d5dab31-5ff8-4ae2-a560-bc4bcba7c9d7', 'tool_call_id': 'toolu_01E5mSaZWm5rWJnCqmt63v4g'}, {'content': 'Based on the weather API results, the current weather in Los Angeles is overcast with a temperature of around 62°F (17°C). There are light winds from the west-southwest around 8-13 mph. The humidity is 65% and visibility is good at 9 miles. Overall, mild spring weather conditions in LA.', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-4d6d4c23-5aad-4042-b0d9-19407a9e08e3', 'example': False, 'tool_calls': [], 'invalid_tool_calls': []}]}\n", + "\n", + "\n", + "\n", + "Receiving new event of type: end...\n", + "None\n", + "\n", + "\n", + "\n" + ] + } + ], + "source": [ + "input = {\"messages\": [{\"role\": \"human\", \"content\": \"whats the weather in la\"}]}\n", + "thread = await client.threads.create()\n", + "async for chunk in client.runs.stream(thread['thread_id'], assistant['assistant_id'], input=input):\n", + " print(f\"Receiving new event of type: {chunk.event}...\")\n", + " print(chunk.data)\n", + " print(\"\\n\\n\")" + ] + }, + { + "cell_type": "markdown", + "id": "43e4432d-e96c-4ae4-8085-866fb57bbcb3", + "metadata": {}, + "source": [ + "If we want to just get the final result, we can use this endpoint and just keep track of the last value we received" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "d2560481-d161-4d4f-b385-4977696c4aa1", + "metadata": {}, + "outputs": [], + "source": [ + "input = {\"messages\": [{\"role\": \"human\", \"content\": \"whats the weather in la\"}]}\n", + "thread = await client.threads.create()\n", + "final_answer = None\n", + "async for chunk in client.runs.stream(thread['thread_id'], assistant['assistant_id'], input=input):\n", + " if chunk.event == \"values\":\n", + " final_answer = chunk.data" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "9c2d60ea-450f-45cd-b867-0cbb162528f6", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'messages': [{'content': 'whats the weather in la',\n", + " 'additional_kwargs': {},\n", + " 'response_metadata': {},\n", + " 'type': 'human',\n", + " 'name': None,\n", + " 'id': 'e78c2f94-d810-42fc-a399-11f6bb1b1092',\n", + " 'example': False},\n", + " {'content': [{'id': 'toolu_01SBMoAGr4U9x3ibztm2UUom',\n", + " 'input': {'query': 'weather in los angeles'},\n", + " 'name': 'tavily_search_results_json',\n", + " 'type': 'tool_use'}],\n", + " 'additional_kwargs': {},\n", + " 'response_metadata': {},\n", + " 'type': 'ai',\n", + " 'name': None,\n", + " 'id': 'run-80767ab8-09fc-40ec-9e45-657ddef5e0b1',\n", + " 'example': False,\n", + " 'tool_calls': [{'name': 'tavily_search_results_json',\n", + " 'args': {'query': 'weather in los angeles'},\n", + " 'id': 'toolu_01SBMoAGr4U9x3ibztm2UUom'}],\n", + " 'invalid_tool_calls': []},\n", + " {'content': '[{\"url\": \"https://www.weatherapi.com/\", \"content\": \"{\\'location\\': {\\'name\\': \\'Los Angeles\\', \\'region\\': \\'California\\', \\'country\\': \\'United States of America\\', \\'lat\\': 34.05, \\'lon\\': -118.24, \\'tz_id\\': \\'America/Los_Angeles\\', \\'localtime_epoch\\': 1716310320, \\'localtime\\': \\'2024-05-21 9:52\\'}, \\'current\\': {\\'last_updated_epoch\\': 1716309900, \\'last_updated\\': \\'2024-05-21 09:45\\', \\'temp_c\\': 16.7, \\'temp_f\\': 62.1, \\'is_day\\': 1, \\'condition\\': {\\'text\\': \\'Overcast\\', \\'icon\\': \\'//cdn.weatherapi.com/weather/64x64/day/122.png\\', \\'code\\': 1009}, \\'wind_mph\\': 8.1, \\'wind_kph\\': 13.0, \\'wind_degree\\': 250, \\'wind_dir\\': \\'WSW\\', \\'pressure_mb\\': 1015.0, \\'pressure_in\\': 29.97, \\'precip_mm\\': 0.0, \\'precip_in\\': 0.0, \\'humidity\\': 65, \\'cloud\\': 100, \\'feelslike_c\\': 16.7, \\'feelslike_f\\': 62.1, \\'vis_km\\': 16.0, \\'vis_miles\\': 9.0, \\'uv\\': 5.0, \\'gust_mph\\': 12.5, \\'gust_kph\\': 20.2}}\"}]',\n", + " 'additional_kwargs': {},\n", + " 'response_metadata': {},\n", + " 'type': 'tool',\n", + " 'name': 'tavily_search_results_json',\n", + " 'id': 'af25e94a-c119-48c3-bbd3-096e42f472ac',\n", + " 'tool_call_id': 'toolu_01SBMoAGr4U9x3ibztm2UUom'},\n", + " {'content': 'Based on the weather API results, the current weather in Los Angeles is overcast with a temperature of around 62°F (17°C). There are light winds from the west-southwest around 8-13 mph. The humidity is 65% and visibility is good at 9 miles. Overall, mild spring weather conditions in LA.',\n", + " 'additional_kwargs': {},\n", + " 'response_metadata': {},\n", + " 'type': 'ai',\n", + " 'name': None,\n", + " 'id': 'run-b90f0037-e56a-4f3b-ad92-00d10d079a9e',\n", + " 'example': False,\n", + " 'tool_calls': [],\n", + " 'invalid_tool_calls': []}]}" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "final_answer" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "39cedbff-0a7f-4a3e-bfc1-595797358769", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.1" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/sdk/img/graph_diagram.png b/examples/sdk/img/graph_diagram.png new file mode 100644 index 000000000..2105cde69 Binary files /dev/null and b/examples/sdk/img/graph_diagram.png differ diff --git a/examples/sdk/img/thread_diagram.png b/examples/sdk/img/thread_diagram.png new file mode 100644 index 000000000..ec938ac6f Binary files /dev/null and b/examples/sdk/img/thread_diagram.png differ diff --git a/examples/sdk/js_sdk.ipynb b/examples/sdk/js_sdk.ipynb new file mode 100644 index 000000000..4110ebb1b --- /dev/null +++ b/examples/sdk/js_sdk.ipynb @@ -0,0 +1,24 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## JS SDK\n", + "Coming soon" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [] + } + ], + "metadata": { + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/examples/sdk/python_sdk.ipynb b/examples/sdk/python_sdk.ipynb new file mode 100644 index 000000000..98e0fc283 --- /dev/null +++ b/examples/sdk/python_sdk.ipynb @@ -0,0 +1,617 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Initialization\n", + "\n", + "### Initializing client\n", + "\n", + "To get started we need to initialize our client. The process for initializing our client is almost identical for both the local deployment and cloud deployment using Langsmith." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "from langgraph_sdk import get_client\n", + "\n", + "# If you deployed using Langsmith use this option\n", + "# Find this url on your Langsmith deployment page\n", + "example_deployed_url = (\n", + " \"https://ht-unhealthy-buffalo25-39d00f953458585aa9f7b5a4fa-g3ps4aazkq-uc.a.run.app\"\n", + ")\n", + "\n", + "# If you deployed locally using langgraph up -c langgraph.json use this option\n", + "# This is the default URL, and you can just call get_client() to use it\n", + "example_local_url = \"http://localhost:8123\"\n", + "\n", + "client = get_client(url=\"whatever-your-url-is\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Selecting an Assistant\n", + "\n", + "To select an assistant we can search the assistants that are hosted on our client, and then select the one we want," + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "assistants = await client.assistants.search()\n", + "assistants = [a for a in assistants if not a[\"config\"]]\n", + "assistant = assistants[0]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In our example we are only hosting a single assistant, but you have the option to host many, in which case you will most likely want to do more filtering than just selecting the first one. Each assistant is a JSON object with the following format, allowing you to select based on a variety of parameters." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca',\n", + " 'graph_id': 'agent',\n", + " 'created_at': '2024-06-11T20:12:45.862108+00:00',\n", + " 'updated_at': '2024-06-11T20:12:45.862108+00:00',\n", + " 'config': {},\n", + " 'metadata': {'created_by': 'system'}}" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "assistant" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Creating a thread\n", + "\n", + "Threads are what we will actually use to run our graphs (assistants). Each thread will update the same state for the graph, meaning we can run the graph multiple times while the state will persist. We can also look back at our thread history, add meta data to different steps of our thread, and update the thread state manually if we wish. We will dive into all of those topics later in this article, but for now let’s just see how to start a thread:" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [], + "source": [ + "thread = await client.threads.create()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can examine the structure of our thread, which similar to the assistants object provides us with some information about the thread itself, including its id, timestamps, and metadata:" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'thread_id': '6c2e8002-5712-4388-bed6-0747e9a86e31',\n", + " 'created_at': '2024-06-19T15:58:59.243657+00:00',\n", + " 'updated_at': '2024-06-19T15:58:59.243657+00:00',\n", + " 'metadata': {}}" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "thread" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we are ready to actually use our graph!\n", + "\n", + "## Invoking the graph\n", + "\n", + "The graph used in this example is a simple example of a StateGraph, but it allows us to show most of the API functionality. The state of our graph is defined as follows:" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [], + "source": [ + "from typing import Annotated, TypedDict\n", + "\n", + "from langchain_core.messages import AnyMessage\n", + "\n", + "from langgraph.graph import add_messages\n", + "\n", + "\n", + "def update_user_info(old_info, new_info):\n", + " if \"name\" not in new_info or new_info[\"age\"] == -1:\n", + " return old_info\n", + " return new_info\n", + "\n", + "\n", + "class UserInformation(TypedDict):\n", + " age: int\n", + " name: str\n", + "\n", + "\n", + "class AgentState(TypedDict):\n", + " messages: Annotated[list[AnyMessage], add_messages]\n", + " user_info: Annotated[UserInformation, update_user_info]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "It is important to note that our member variables can be updated by using the `Annotated` class. This is especially important when we make API calls that will update our variables. This is also a good example that your graph can hold much more information than just messages. In our example we use a very simple `UserInformation` class, but you can imagine holding much richer information in your state.\n", + "\n", + "Our graph looks like follows:\n", + "\n", + "
\n", + " \n", + "
\n", + "\n", + "The workflow is as follows: first the user inputs some message, our llm decides how to configure the call to our tool `get_user_info` , and after getting the results of the tool call we respond to our user using another LLM.\n", + "\n", + "### Simple Invocation\n", + "\n", + "Ok, now that we have set up our client, assistant, and thread we can actually invoke the graph above. Let’s first define the function we will use to invoke the graph, since we don’t want to have to rewrite this code every single run." + ] + }, + { + "cell_type": "code", + "execution_count": 48, + "metadata": {}, + "outputs": [], + "source": [ + "async def run_input(client, thread, assistant, input, metadata={}):\n", + " # client.runs.stream will stream the results of running our graph\n", + " async for chunk in client.runs.stream(\n", + " thread[\"thread_id\"],\n", + " assistant[\"assistant_id\"],\n", + " input=input,\n", + " config={\"configurable\": metadata},\n", + " stream_mode=\"updates\",\n", + " ):\n", + " if chunk.data and \"run_id\" not in chunk.data:\n", + " print(chunk.data)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let’s now see what happens to our graph when we run it with a simple sentence:" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'llm': {'messages': [{'content': '', 'additional_kwargs': {'tool_calls': [{'index': 0, 'id': 'call_ioeUDw39bXQ2nap5f593xZMW', 'function': {'arguments': '{\"age\":26,\"name\":\"Bagatur\"}', 'name': 'PersonalInfo'}, 'type': 'function'}]}, 'response_metadata': {'finish_reason': 'stop'}, 'type': 'ai', 'name': None, 'id': 'run-75a0d9db-df99-4b0c-b356-f24e76f5ca5a', 'example': False, 'tool_calls': [{'name': 'PersonalInfo', 'args': {'age': 26, 'name': 'Bagatur'}, 'id': 'call_ioeUDw39bXQ2nap5f593xZMW'}], 'invalid_tool_calls': [], 'usage_metadata': None}]}}\n", + "{'get_user_info': {'messages': [{'content': 'Hello! My name is Bagatur and I am 26 years old.', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': 'abafa365-1280-439d-b2ac-d4c547284ff9', 'example': False}, {'content': '', 'additional_kwargs': {'tool_calls': [{'index': 0, 'id': 'call_E6NK18NXOHFkp8CeIuF7iI3K', 'function': {'arguments': '{\"age\":26,\"name\":\"Bagatur\"}', 'name': 'PersonalInfo'}, 'type': 'function'}]}, 'response_metadata': {'finish_reason': 'stop'}, 'type': 'ai', 'name': None, 'id': 'run-c018c6a2-b93a-4787-aae3-46df0ef24c5b', 'example': False, 'tool_calls': [{'name': 'PersonalInfo', 'args': {'age': 26, 'name': 'Bagatur'}, 'id': 'call_E6NK18NXOHFkp8CeIuF7iI3K'}], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': 'Hello Bagatur! How can I assist you today?', 'additional_kwargs': {}, 'response_metadata': {'finish_reason': 'stop'}, 'type': 'ai', 'name': None, 'id': 'run-fb1ac0ca-b1f8-454b-ba3f-849c266dcc05', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': 'Hello! What is my name?', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '8bf0637b-8214-4c85-85ff-873ec193a7b8', 'example': False}, {'content': '', 'additional_kwargs': {'tool_calls': [{'index': 0, 'id': 'call_6EFvGDGWHuoOKm4p7dq3qazN', 'function': {'arguments': '{\"age\":-1,\"name\":\"John Doe\"}', 'name': 'PersonalInfo'}, 'type': 'function'}]}, 'response_metadata': {'finish_reason': 'stop'}, 'type': 'ai', 'name': None, 'id': 'run-30a91dfa-4526-4bb7-b4a2-017e5c530ed2', 'example': False, 'tool_calls': [{'name': 'PersonalInfo', 'args': {'age': -1, 'name': 'John Doe'}, 'id': 'call_6EFvGDGWHuoOKm4p7dq3qazN'}], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': 'Your name is Bagatur. How can I assist you today?', 'additional_kwargs': {}, 'response_metadata': {'finish_reason': 'stop'}, 'type': 'ai', 'name': None, 'id': 'run-f3dcd3ad-b438-4011-9353-83978758ee81', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': 'Hello! My name is Bagatur and I am 26 years old.', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '88e7bb12-552d-4607-8bc5-ce2b342f0604', 'example': False}, {'content': '', 'additional_kwargs': {'tool_calls': [{'index': 0, 'id': 'call_ioeUDw39bXQ2nap5f593xZMW', 'function': {'arguments': '{\"age\":26,\"name\":\"Bagatur\"}', 'name': 'PersonalInfo'}, 'type': 'function'}]}, 'response_metadata': {'finish_reason': 'stop'}, 'type': 'ai', 'name': None, 'id': 'run-75a0d9db-df99-4b0c-b356-f24e76f5ca5a', 'example': False, 'tool_calls': [{'name': 'PersonalInfo', 'args': {'age': 26, 'name': 'Bagatur'}, 'id': 'call_ioeUDw39bXQ2nap5f593xZMW'}], 'invalid_tool_calls': [], 'usage_metadata': None}], 'user_info': {'age': 26, 'name': 'Bagatur'}}}\n", + "{'respond_to_user': {'messages': [{'content': 'Hello Bagatur! How can I assist you today?', 'additional_kwargs': {}, 'response_metadata': {'finish_reason': 'stop'}, 'type': 'ai', 'name': None, 'id': 'run-49176b69-aae5-41ac-8c1e-7bab373b2b9b', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]}}\n" + ] + } + ], + "source": [ + "input = {\n", + " \"messages\": [\n", + " {\"role\": \"user\", \"content\": \"Hello! My name is Bagatur and I am 26 years old.\"}\n", + " ]\n", + "}\n", + "\n", + "await run_input(client, thread, assistant, input)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can see our graph ran as expected, by calling our three nodes sequentially. Let’s now examine the state to take a further look under the hood. We can get the state by using the following command: " + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "dict_keys(['values', 'next', 'config', 'metadata', 'created_at', 'parent_config'])" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "state = await client.threads.get_state(thread_id=thread[\"thread_id\"])\n", + "state.keys()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Our state variable contains a variety of important information. Here is a quick summary of the keys and what they represent:\n", + "\n", + "- `values` contains the actual state values, so in our case you could call `state['values']['messages']` or `state['values']['user_info']` and get the actual values of each of the state variables.\n", + "- `next` tells us what action in the graph is next at the current state. Since we just finished running our graph and reached the end node, it is currently empty because there is no next action to take. However, if you go through the state at each point of the run you will see that the `next` value goes from `__start__` → `llm` → `get_user_info` →`respond_to_user` .\n", + "- `metadata` stores the metadata associated with our state. This is data that is outside of the agent state, but is important to keep track of across multiple runs. An example of this is shown in the next section.\n", + "- `config` tells us what the configuration of the state is. This is important for when we want to run a query starting at a previous state instead of the one we are at. An example of this is shown in the Invoking from a previous checkpoint section\n", + "\n", + "### Invoking with Metadata\n", + "\n", + "Let’s create a new thread to reset our state and start fresh." + ] + }, + { + "cell_type": "code", + "execution_count": 49, + "metadata": {}, + "outputs": [], + "source": [ + "thread = await client.threads.create()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now let’s add some metadata to our request. In this example we are going to treat each run of our assistant as a separate “node”. For each run, we will pass in a “node_id” as well as a “parent_node” in the metadata. This way we can easily go “back in time” and rerun our graph from a previous checkpoint. \n", + "\n", + "> NOTE: The reason we add this metadata instead of using the `parent_config` attribute is because `parent_config` tracks every individual step of a run, not the entire run itself.\n", + ">" + ] + }, + { + "cell_type": "code", + "execution_count": 50, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'llm': {'messages': [{'content': '', 'additional_kwargs': {'tool_calls': [{'index': 0, 'id': 'call_VrA7UKg2w99BvIpsqrwjoawk', 'function': {'arguments': '{\"age\":26,\"name\":\"Bagatur\"}', 'name': 'PersonalInfo'}, 'type': 'function'}]}, 'response_metadata': {'finish_reason': 'stop'}, 'type': 'ai', 'name': None, 'id': 'run-067609c1-4f1a-4d6d-bc1c-224a29153d37', 'example': False, 'tool_calls': [{'name': 'PersonalInfo', 'args': {'age': 26, 'name': 'Bagatur'}, 'id': 'call_VrA7UKg2w99BvIpsqrwjoawk'}], 'invalid_tool_calls': [], 'usage_metadata': None}]}}\n", + "{'get_user_info': {'messages': [{'content': 'Hello! My name is Bagatur and I am 26 years old.', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '05faf88c-9f85-462e-84ee-4667de35625d', 'example': False}, {'content': '', 'additional_kwargs': {'tool_calls': [{'index': 0, 'id': 'call_VrA7UKg2w99BvIpsqrwjoawk', 'function': {'arguments': '{\"age\":26,\"name\":\"Bagatur\"}', 'name': 'PersonalInfo'}, 'type': 'function'}]}, 'response_metadata': {'finish_reason': 'stop'}, 'type': 'ai', 'name': None, 'id': 'run-067609c1-4f1a-4d6d-bc1c-224a29153d37', 'example': False, 'tool_calls': [{'name': 'PersonalInfo', 'args': {'age': 26, 'name': 'Bagatur'}, 'id': 'call_VrA7UKg2w99BvIpsqrwjoawk'}], 'invalid_tool_calls': [], 'usage_metadata': None}], 'user_info': {'age': 26, 'name': 'Bagatur'}}}\n", + "{'respond_to_user': {'messages': [{'content': 'Hello Bagatur! How can I assist you today?', 'additional_kwargs': {}, 'response_metadata': {'finish_reason': 'stop'}, 'type': 'ai', 'name': None, 'id': 'run-48041247-fe04-419f-9f12-511e28c5f8aa', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]}}\n" + ] + } + ], + "source": [ + "input = {\n", + " \"messages\": [\n", + " {\"role\": \"user\", \"content\": \"Hello! My name is Bagatur and I am 26 years old.\"}\n", + " ]\n", + "}\n", + "\n", + "metadata = {\"node_id\": 1, \"parent_node\": None}\n", + "\n", + "await run_input(client, thread, assistant, input, metadata)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Using our `run_input` function makes it easy to pass in metadata and you can inspect the function as well as the API docs to see exactly how metadata gets passed.\n", + "\n", + "We can continue our thread by creating a second node as follows:" + ] + }, + { + "cell_type": "code", + "execution_count": 51, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'llm': {'messages': [{'content': '', 'additional_kwargs': {'tool_calls': [{'index': 0, 'id': 'call_b2T2e0doVrh6uB1aHLIQglYR', 'function': {'arguments': '{\"age\":-1,\"name\":\"John Doe\"}', 'name': 'PersonalInfo'}, 'type': 'function'}]}, 'response_metadata': {'finish_reason': 'stop'}, 'type': 'ai', 'name': None, 'id': 'run-808dca3a-0188-4c6b-9fc7-0037949fd820', 'example': False, 'tool_calls': [{'name': 'PersonalInfo', 'args': {'age': -1, 'name': 'John Doe'}, 'id': 'call_b2T2e0doVrh6uB1aHLIQglYR'}], 'invalid_tool_calls': [], 'usage_metadata': None}]}}\n", + "{'get_user_info': {'messages': [{'content': 'Hello! My name is Bagatur and I am 26 years old.', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '05faf88c-9f85-462e-84ee-4667de35625d', 'example': False}, {'content': '', 'additional_kwargs': {'tool_calls': [{'index': 0, 'id': 'call_VrA7UKg2w99BvIpsqrwjoawk', 'function': {'arguments': '{\"age\":26,\"name\":\"Bagatur\"}', 'name': 'PersonalInfo'}, 'type': 'function'}]}, 'response_metadata': {'finish_reason': 'stop'}, 'type': 'ai', 'name': None, 'id': 'run-067609c1-4f1a-4d6d-bc1c-224a29153d37', 'example': False, 'tool_calls': [{'name': 'PersonalInfo', 'args': {'age': 26, 'name': 'Bagatur'}, 'id': 'call_VrA7UKg2w99BvIpsqrwjoawk'}], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': 'Hello Bagatur! How can I assist you today?', 'additional_kwargs': {}, 'response_metadata': {'finish_reason': 'stop'}, 'type': 'ai', 'name': None, 'id': 'run-48041247-fe04-419f-9f12-511e28c5f8aa', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': 'Hello! What is my name?', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '0970d6dd-e071-49bf-bf71-5345185b8291', 'example': False}, {'content': '', 'additional_kwargs': {'tool_calls': [{'index': 0, 'id': 'call_b2T2e0doVrh6uB1aHLIQglYR', 'function': {'arguments': '{\"age\":-1,\"name\":\"John Doe\"}', 'name': 'PersonalInfo'}, 'type': 'function'}]}, 'response_metadata': {'finish_reason': 'stop'}, 'type': 'ai', 'name': None, 'id': 'run-808dca3a-0188-4c6b-9fc7-0037949fd820', 'example': False, 'tool_calls': [{'name': 'PersonalInfo', 'args': {'age': -1, 'name': 'John Doe'}, 'id': 'call_b2T2e0doVrh6uB1aHLIQglYR'}], 'invalid_tool_calls': [], 'usage_metadata': None}], 'user_info': {'age': -1, 'name': 'John Doe'}}}\n", + "{'respond_to_user': {'messages': [{'content': 'Your name is Bagatur. How can I assist you today, Bagatur?', 'additional_kwargs': {}, 'response_metadata': {'finish_reason': 'stop'}, 'type': 'ai', 'name': None, 'id': 'run-9ada26f0-951e-4dd5-a2c5-4357bdeeac9d', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]}}\n" + ] + } + ], + "source": [ + "input = {\"messages\": [{\"role\": \"user\", \"content\": \"Hello! What is my name?\"}]}\n", + "metadata = {\"node_id\": 2, \"parent_node\": 1}\n", + "\n", + "await run_input(client, thread, assistant, input, metadata)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Perfect! The state persisted across separate runs, and the LLM remembers the name of our user. In a future we will explore non-sequential runs, i.e. not having each run just follow the last one but choosing which checkpoint we start our run from.\n", + "\n", + "## Querying and Updating the thread\n", + "\n", + "### Getting checkpoints by metadata\n", + "\n", + "Let’s say we want to start a new run from a previous state (not the current state). This state lives somewhere in our history, so we can utilize the `get_history` function to try and find it." + ] + }, + { + "cell_type": "code", + "execution_count": 52, + "metadata": {}, + "outputs": [], + "source": [ + "history = await client.threads.get_history(thread_id=thread[\"thread_id\"])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This is helpful for inspecting the specifics of our current thread, but remember that the history contains all the intermediate steps a graph takes. In our case, where the graph has 5 nodes (remember that Start and End both count as nodes), our history array grows quickly. Luckily, there is a way to query by using metadata. For example if we wanted to start a run from Node 1(from the example from above) we need to find the state from the end of run with metadata node_id:1 , which we can do like so:" + ] + }, + { + "cell_type": "code", + "execution_count": 53, + "metadata": {}, + "outputs": [], + "source": [ + "node_1_history = await client.threads.get_history(\n", + " thread_id=thread[\"thread_id\"], metadata={\"node_id\": 1}\n", + ")\n", + "# At the end of the run there will be no 'next' for the graph to execute\n", + "node_1_end_of_run = [h for h in node_1_history if h[\"next\"] == []][0]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now let’s explore how we could use this information to create a new branch in our thread.\n", + "\n", + "### Invoking from a previous checkpoint\n", + "\n", + "The following diagram describes what we would like to happen:\n", + "\n", + "
\n", + " \n", + "
\n", + "Basically, we want to have 3 runs of our graph, but instead of having them sequentially - we want both the second and third run to originate from the same state. We can do this by utilizing the code we used above, and passing additional metadata to our run." + ] + }, + { + "cell_type": "code", + "execution_count": 54, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'llm': {'messages': [{'content': '', 'additional_kwargs': {'tool_calls': [{'index': 0, 'id': 'call_yC7HUAQfcLzheepMD8SnAojR', 'function': {'arguments': '{\"age\":-1}', 'name': 'PersonalInfo'}, 'type': 'function'}]}, 'response_metadata': {'finish_reason': 'stop'}, 'type': 'ai', 'name': None, 'id': 'run-b6a653e2-11ea-4f82-aa6a-ef321deed9ce', 'example': False, 'tool_calls': [{'name': 'PersonalInfo', 'args': {'age': -1}, 'id': 'call_yC7HUAQfcLzheepMD8SnAojR'}], 'invalid_tool_calls': [], 'usage_metadata': None}]}}\n", + "{'get_user_info': {'messages': [{'content': 'Hello! My name is Bagatur and I am 26 years old.', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '05faf88c-9f85-462e-84ee-4667de35625d', 'example': False}, {'content': '', 'additional_kwargs': {'tool_calls': [{'index': 0, 'id': 'call_VrA7UKg2w99BvIpsqrwjoawk', 'function': {'arguments': '{\"age\":26,\"name\":\"Bagatur\"}', 'name': 'PersonalInfo'}, 'type': 'function'}]}, 'response_metadata': {'finish_reason': 'stop'}, 'type': 'ai', 'name': None, 'id': 'run-067609c1-4f1a-4d6d-bc1c-224a29153d37', 'example': False, 'tool_calls': [{'name': 'PersonalInfo', 'args': {'age': 26, 'name': 'Bagatur'}, 'id': 'call_VrA7UKg2w99BvIpsqrwjoawk'}], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': 'Hello Bagatur! How can I assist you today?', 'additional_kwargs': {}, 'response_metadata': {'finish_reason': 'stop'}, 'type': 'ai', 'name': None, 'id': 'run-48041247-fe04-419f-9f12-511e28c5f8aa', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': 'Hello! What is my age?', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': 'e94fa535-f821-4bfb-9d96-68c414adad8d', 'example': False}, {'content': '', 'additional_kwargs': {'tool_calls': [{'index': 0, 'id': 'call_yC7HUAQfcLzheepMD8SnAojR', 'function': {'arguments': '{\"age\":-1}', 'name': 'PersonalInfo'}, 'type': 'function'}]}, 'response_metadata': {'finish_reason': 'stop'}, 'type': 'ai', 'name': None, 'id': 'run-b6a653e2-11ea-4f82-aa6a-ef321deed9ce', 'example': False, 'tool_calls': [{'name': 'PersonalInfo', 'args': {'age': -1}, 'id': 'call_yC7HUAQfcLzheepMD8SnAojR'}], 'invalid_tool_calls': [], 'usage_metadata': None}], 'user_info': {'age': -1}}}\n", + "{'respond_to_user': {'messages': [{'content': 'You are 26 years old, Bagatur! How can I assist you today?', 'additional_kwargs': {}, 'response_metadata': {'finish_reason': 'stop'}, 'type': 'ai', 'name': None, 'id': 'run-e3b6e627-b4b1-4249-a1bc-a6359ad7b961', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]}}\n" + ] + } + ], + "source": [ + "input = {\"messages\": [{\"role\": \"user\", \"content\": \"Hello! What is my age?\"}]}\n", + "metadata = {\n", + " **{\"node_id\": 3, \"parent_node\": 1},\n", + " **{\n", + " \"thread_ts\": node_1_end_of_run[\"checkpoint_id\"],\n", + " \"thread_id\": thread[\"thread_id\"],\n", + " },\n", + "}\n", + "\n", + "await run_input(client, thread, assistant, input, metadata)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To check that everything actually worked as planned, let’s check our current state and check that message history to ensure that the message we passed to Node 2 is nowhere to be found." + ] + }, + { + "cell_type": "code", + "execution_count": 55, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "False" + ] + }, + "execution_count": 55, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "\"Hello! What is my name?\" in [\n", + " message[\"content\"] for message in state[\"values\"][\"messages\"]\n", + "]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Great! This has worked as expected. Being able to go back to previous states and execute new graph runs from those checkpoints is a great way to develop flexible applications that don’t require reloading or restarting everything when an error is detected or a user changes their mind.\n", + "\n", + "### Updating/Patching the thread state\n", + "\n", + "Lastly, let’s discuss the ability to manually change both the thread state as well as the metadata for a given state. Let’s say we incorrectly inputted data to the LLM and we want to rectify it. \n", + "\n", + "Continuing our previous example, let’s say the user mistyped their age and we want to let the graph know that without actually running it. In this case we can rectify this by using `update_state`" + ] + }, + { + "cell_type": "code", + "execution_count": 56, + "metadata": {}, + "outputs": [], + "source": [ + "new_state = await client.threads.update_state(\n", + " thread_id=thread[\"thread_id\"], values={\"user_info\": {\"name\": \"Bagatur\", \"age\": 35}}\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let’s make sure that the state did in fact update and ask our LLM again how old we are by invoking the graph again:" + ] + }, + { + "cell_type": "code", + "execution_count": 57, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'llm': {'messages': [{'content': '', 'additional_kwargs': {'tool_calls': [{'index': 0, 'id': 'call_Ph3tYdt2UNdwqAF3kVL198Bg', 'function': {'arguments': '{\"age\":-1}', 'name': 'PersonalInfo'}, 'type': 'function'}]}, 'response_metadata': {'finish_reason': 'stop'}, 'type': 'ai', 'name': None, 'id': 'run-373f3e22-8549-4f77-9ff2-05133099bb09', 'example': False, 'tool_calls': [{'name': 'PersonalInfo', 'args': {'age': -1}, 'id': 'call_Ph3tYdt2UNdwqAF3kVL198Bg'}], 'invalid_tool_calls': [], 'usage_metadata': None}]}}\n", + "{'get_user_info': {'messages': [{'content': 'Hello! My name is Bagatur and I am 26 years old.', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '05faf88c-9f85-462e-84ee-4667de35625d', 'example': False}, {'content': '', 'additional_kwargs': {'tool_calls': [{'index': 0, 'id': 'call_VrA7UKg2w99BvIpsqrwjoawk', 'function': {'arguments': '{\"age\":26,\"name\":\"Bagatur\"}', 'name': 'PersonalInfo'}, 'type': 'function'}]}, 'response_metadata': {'finish_reason': 'stop'}, 'type': 'ai', 'name': None, 'id': 'run-067609c1-4f1a-4d6d-bc1c-224a29153d37', 'example': False, 'tool_calls': [{'name': 'PersonalInfo', 'args': {'age': 26, 'name': 'Bagatur'}, 'id': 'call_VrA7UKg2w99BvIpsqrwjoawk'}], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': 'Hello Bagatur! How can I assist you today?', 'additional_kwargs': {}, 'response_metadata': {'finish_reason': 'stop'}, 'type': 'ai', 'name': None, 'id': 'run-48041247-fe04-419f-9f12-511e28c5f8aa', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': 'Hello! What is my age?', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': 'e94fa535-f821-4bfb-9d96-68c414adad8d', 'example': False}, {'content': '', 'additional_kwargs': {'tool_calls': [{'index': 0, 'id': 'call_yC7HUAQfcLzheepMD8SnAojR', 'function': {'arguments': '{\"age\":-1}', 'name': 'PersonalInfo'}, 'type': 'function'}]}, 'response_metadata': {'finish_reason': 'stop'}, 'type': 'ai', 'name': None, 'id': 'run-b6a653e2-11ea-4f82-aa6a-ef321deed9ce', 'example': False, 'tool_calls': [{'name': 'PersonalInfo', 'args': {'age': -1}, 'id': 'call_yC7HUAQfcLzheepMD8SnAojR'}], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': 'You are 26 years old, Bagatur! How can I assist you today?', 'additional_kwargs': {}, 'response_metadata': {'finish_reason': 'stop'}, 'type': 'ai', 'name': None, 'id': 'run-e3b6e627-b4b1-4249-a1bc-a6359ad7b961', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': 'Hello! What is my age?', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '24ac02be-7b08-4e91-9d20-90507ed25391', 'example': False}, {'content': '', 'additional_kwargs': {'tool_calls': [{'index': 0, 'id': 'call_Ph3tYdt2UNdwqAF3kVL198Bg', 'function': {'arguments': '{\"age\":-1}', 'name': 'PersonalInfo'}, 'type': 'function'}]}, 'response_metadata': {'finish_reason': 'stop'}, 'type': 'ai', 'name': None, 'id': 'run-373f3e22-8549-4f77-9ff2-05133099bb09', 'example': False, 'tool_calls': [{'name': 'PersonalInfo', 'args': {'age': -1}, 'id': 'call_Ph3tYdt2UNdwqAF3kVL198Bg'}], 'invalid_tool_calls': [], 'usage_metadata': None}], 'user_info': {'age': -1}}}\n", + "{'respond_to_user': {'messages': [{'content': 'My apologies for the confusion earlier. You are 35 years old, Bagatur! How can I assist you today?', 'additional_kwargs': {}, 'response_metadata': {'finish_reason': 'stop'}, 'type': 'ai', 'name': None, 'id': 'run-298306df-34a5-459a-a8c3-a0f16440c429', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]}}\n" + ] + } + ], + "source": [ + "input = {\"messages\": [{\"role\": \"user\", \"content\": \"Hello! What is my age?\"}]}\n", + "\n", + "await run_input(client, thread, assistant, input)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Voila! The LLM knows our users age updated without us having to prompt it at all.\n", + "\n", + "The last thing we will talk about is patching the thread, which is used when we want to update the metadata of a state. For example, say we actually wanted to update our last state to have `node_id:4` instead of `node_id:3`. To do this, we can call:" + ] + }, + { + "cell_type": "code", + "execution_count": 58, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'configurable': {'thread_id': '4f044e5a-6f6e-4663-923e-6333c052ce9f',\n", + " 'thread_ts': '1ef2e5d6-e39f-6d26-800e-22245f8220a7'}}" + ] + }, + "execution_count": 58, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "await client.threads.patch_state(thread_id=thread[\"thread_id\"], metadata={\"node_id\": 4})" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can check that this worked by checking the metadata of our state" + ] + }, + { + "cell_type": "code", + "execution_count": 59, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Current node id is 4\n" + ] + } + ], + "source": [ + "state = await client.threads.get_state(thread_id=thread[\"thread_id\"])\n", + "print(f\"Current node id is {state['metadata']['node_id']}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Perfect! The patch worked as expected." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.9" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +}