diff --git a/docs/docs/cloud/concepts/api.md b/docs/docs/cloud/concepts/api.md index e81c672f3..99bd6c9ab 100644 --- a/docs/docs/cloud/concepts/api.md +++ b/docs/docs/cloud/concepts/api.md @@ -39,7 +39,7 @@ It's often useful to run graphs on some schedule. LangGraph Cloud supports cron - Create a new thread with the specified assistant - Send the specified input to that thread -Note that this sends the same input to the thread every time. See the [how-to guide](../how-tos/cloud_examples/cron_jobs.ipynb) for creating cron jobs. +Note that this sends the same input to the thread every time. See the [how-to guide](../how-tos/cron_jobs.md) for creating cron jobs. The LangGraph Cloud API provides several endpoints for creating and managing cron jobs. See the [API reference](../reference/api/api_ref.html#tag/runscreate/POST/threads/{thread_id}/runs/crons) for more details. @@ -182,13 +182,13 @@ The only difference is in stateless background runs, if the task worker dies hal - whereas a stateful background run would retry from the last successful checkpoint - a stateless background run would retry from the beginning -See the [how-to guide](../how-tos/cloud_examples/stateless_runs.ipynb) for creating stateless runs. +See the [how-to guide](../how-tos/stateless_runs.md) for creating stateless runs. ### Webhooks For all types of runs, langgraph cloud supports completion webhooks. When you create the run you can pass a webhook URL to be called when the completes (successfully or not). This is especially useful for background runs and cron jobs, as the webhook can give you an indication the run has completed and you can perform further actions for your appilcation. -See this [how-to guide](../how-tos/cloud_examples/webhooks.ipynb) to learn about how to use webhooks with LangGraph Cloud. +See this [how-to guide](../how-tos/webhooks.md) to learn about how to use webhooks with LangGraph Cloud. ## Deployment diff --git a/docs/docs/cloud/deployment/test_locally.md b/docs/docs/cloud/deployment/test_locally.md index a5a06624a..2822b4052 100644 --- a/docs/docs/cloud/deployment/test_locally.md +++ b/docs/docs/cloud/deployment/test_locally.md @@ -49,6 +49,7 @@ You can either initialize by passing authentication or by setting an environment # only pass the url argument to get_client() if you changed the default port when calling langgraph up client = get_client(url=,api_key=) + # Using the graph deployed with the name "agent" assistant_id = "agent" thread = await client.threads.create() ``` @@ -60,7 +61,8 @@ You can either initialize by passing authentication or by setting an environment // only set the apiUrl if you changed the default port when calling langgraph up const client = new Client({ apiUrl: , apiKey: }); - const assistantId = "agent" + // Using the graph deployed with the name "agent" + const assistantId = "agent"; const thread = await client.threads.create(); ``` @@ -85,6 +87,7 @@ If you have a `LANGCHAIN_API_KEY` set in your environment, you do not need to ex # only pass the url argument to get_client() if you changed the default port when calling langgraph up client = get_client() + # Using the graph deployed with the name "agent" assistant_id = "agent" thread = await client.threads.create() ``` @@ -96,7 +99,8 @@ If you have a `LANGCHAIN_API_KEY` set in your environment, you do not need to ex // only set the apiUrl if you changed the default port when calling langgraph up const client = new Client(); - const assistantId = "agent" + // Using the graph deployed with the name "agent" + const assistantId = "agent"; const thread = await client.threads.create(); ``` diff --git a/docs/docs/cloud/how-tos/background_run.md b/docs/docs/cloud/how-tos/background_run.md new file mode 100644 index 000000000..d648e25b0 --- /dev/null +++ b/docs/docs/cloud/how-tos/background_run.md @@ -0,0 +1,443 @@ +# How to kick off background runs + +This guide covers how to kick off background runs for your agent. +This can be useful for long running jobs. + +First let's set up our client and thread: + +=== "Python" + + ```python + from langgraph_sdk import get_client + + client = get_client(url=) + # Using the graph deployed with the name "agent" + assistant_id = "agent" + # create thread + thread = await client.threads.create() + print(thread) + ``` + +=== "Javascript" + + ```js + import { Client } from "@langchain/langgraph-sdk"; + + const client = new Client({ apiUrl: }); + // Using the graph deployed with the name "agent" + const assistantID = "agent"; + // create thread + const thread = await client.threads.create(); + console.log(thread); + ``` + +=== "CURL" + + ```bash + curl --request POST \ + --url /threads \ + --header 'Content-Type: application/json' + ``` + +Output: + + { + 'thread_id': '5cb1e8a1-34b3-4a61-a34e-71a9799bd00d', + 'created_at': '2024-08-30T20:35:52.062934+00:00', + 'updated_at': '2024-08-30T20:35:52.062934+00:00', + 'metadata': {}, + 'status': 'idle', + 'config': {}, + 'values': None + } + +If we list the current runs on this thread, we will see that it's empty: + +=== "Python" + + ```python + runs = await client.runs.list(thread["thread_id"]) + print(runs) + ``` + +=== "Javascript" + + ```js + let runs = await client.runs.list(thread['thread_id']); + console.log(runs); + ``` + +=== "CURL" + + ```bash + curl --request GET \ + --url /threads//runs + ``` + +Output: + + [] + +Now let's kick off a run: + +=== "Python" + + ```python + input = {"messages": [{"role": "human", "content": "what's the weather in sf"}]} + run = await client.runs.create(thread["thread_id"], assistant_id, input=input) + ``` + +=== "Javascript" + + ```js + let input = {"messages": [{"role": "human", "content": "what's the weather in sf"}]}; + let run = await client.runs.create(thread["thread_id"], assistantID, { input }); + ``` + +=== "CURL" + + ```bash + curl --request POST \ + --url /threads//runs \ + --header 'Content-Type: application/json' \ + --data '{ + "assistant_id": + }' + ``` + +The first time we poll it, we can see `status=pending`: + +=== "Python" + + ```python + print(await client.runs.get(thread["thread_id"], run["run_id"])) + ``` + +=== "Javascript" + + ```js + console.log(await client.runs.get(thread["thread_id"], run["run_id"])); + ``` + +=== "CURL" + + ```bash + curl --request GET \ + --url /threads//runs/ + ``` + +Output: + + { + "run_id": "1ef6a5f8-bd86-6763-bbd6-bff042db7b1b", + "thread_id": "7885f0cf-94ad-4040-91d7-73f7ba007c8a", + "assistant_id": "fe096781-5601-53d2-b2f6-0d3403f7e9ca", + "created_at": "2024-09-04T01:46:47.244887+00:00", + "updated_at": "2024-09-04T01:46:47.244887+00:00", + "metadata": {}, + "status": "pending", + "kwargs": { + "input": { + "messages": [ + { + "role": "human", + "content": "what's the weather in sf" + } + ] + }, + "config": { + "metadata": { + "created_by": "system" + }, + "configurable": { + "run_id": "1ef6a5f8-bd86-6763-bbd6-bff042db7b1b", + "user_id": "", + "graph_id": "agent", + "thread_id": "7885f0cf-94ad-4040-91d7-73f7ba007c8a", + "assistant_id": "fe096781-5601-53d2-b2f6-0d3403f7e9ca", + "checkpoint_id": null + } + }, + "webhook": null, + "temporary": false, + "stream_mode": [ + "values" + ], + "feedback_keys": null, + "interrupt_after": null, + "interrupt_before": null + }, + "multitask_strategy": "reject" + } + + + +Now we can join the run, wait for it to finish and check that status again: + +=== "Python" + + ```python + await client.runs.join(thread["thread_id"], run["run_id"]) + print(await client.runs.get(thread["thread_id"], run["run_id"])) + ``` + +=== "Javascript" + + ```js + await client.runs.join(thread["thread_id"], run["run_id"]); + console.log(await client.runs.get(thread["thread_id"], run["run_id"])); + ``` + +=== "CURL" + + ```bash + curl --request GET \ + --url /threads//runs//join && + curl --request GET \ + --url /threads//runs/ + ``` + +Output: + + { + "run_id": "1ef6a5f8-bd86-6763-bbd6-bff042db7b1b", + "thread_id": "7885f0cf-94ad-4040-91d7-73f7ba007c8a", + "assistant_id": "fe096781-5601-53d2-b2f6-0d3403f7e9ca", + "created_at": "2024-09-04T01:46:47.244887+00:00", + "updated_at": "2024-09-04T01:46:47.244887+00:00", + "metadata": {}, + "status": "success", + "kwargs": { + "input": { + "messages": [ + { + "role": "human", + "content": "what's the weather in sf" + } + ] + }, + "config": { + "metadata": { + "created_by": "system" + }, + "configurable": { + "run_id": "1ef6a5f8-bd86-6763-bbd6-bff042db7b1b", + "user_id": "", + "graph_id": "agent", + "thread_id": "7885f0cf-94ad-4040-91d7-73f7ba007c8a", + "assistant_id": "fe096781-5601-53d2-b2f6-0d3403f7e9ca", + "checkpoint_id": null + } + }, + "webhook": null, + "temporary": false, + "stream_mode": [ + "values" + ], + "feedback_keys": null, + "interrupt_after": null, + "interrupt_before": null + }, + "multitask_strategy": "reject" + } + + +Perfect! The run succeeded as we would expect. We can double check that the run worked as expected by printing out the final state: + +=== "Python" + + ```python + final_result = await client.threads.get_state(thread["thread_id"]) + print(final_result) + ``` + +=== "Javascript" + + ```js + let finalResult = await client.threads.getState(thread["thread_id"]); + console.log(finalResult); + ``` + +=== "CURL" + + ```bash + curl --request GET \ + --url /threads//state + ``` + +Output: + + { + "values": { + "messages": [ + { + "content": "what's the weather in sf", + "additional_kwargs": {}, + "response_metadata": {}, + "type": "human", + "name": null, + "id": "beba31bf-320d-4125-9c37-cadf526ac47a", + "example": false + }, + { + "content": [ + { + "id": "toolu_01AaNPSPzqia21v7aAKwbKYm", + "input": {}, + "name": "tavily_search_results_json", + "type": "tool_use", + "index": 0, + "partial_json": "{\"query\": \"weather in san francisco\"}" + } + ], + "additional_kwargs": {}, + "response_metadata": { + "stop_reason": "tool_use", + "stop_sequence": null + }, + "type": "ai", + "name": null, + "id": "run-f220faf8-1d27-4f73-ad91-6bb3f47e8639", + "example": false, + "tool_calls": [ + { + "name": "tavily_search_results_json", + "args": { + "query": "weather in san francisco" + }, + "id": "toolu_01AaNPSPzqia21v7aAKwbKYm", + "type": "tool_call" + } + ], + "invalid_tool_calls": [], + "usage_metadata": { + "input_tokens": 273, + "output_tokens": 61, + "total_tokens": 334 + } + }, + { + "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': 1725052131, 'localtime': '2024-08-30 14:08'}, 'current': {'last_updated_epoch': 1725051600, 'last_updated': '2024-08-30 14:00', 'temp_c': 21.1, 'temp_f': 70.0, 'is_day': 1, 'condition': {'text': 'Partly cloudy', 'icon': '//cdn.weatherapi.com/weather/64x64/day/116.png', 'code': 1003}, 'wind_mph': 11.9, 'wind_kph': 19.1, 'wind_degree': 290, 'wind_dir': 'WNW', 'pressure_mb': 1018.0, 'pressure_in': 30.07, 'precip_mm': 0.0, 'precip_in': 0.0, 'humidity': 59, 'cloud': 25, 'feelslike_c': 21.1, 'feelslike_f': 70.0, 'windchill_c': 18.6, 'windchill_f': 65.5, 'heatindex_c': 18.6, 'heatindex_f': 65.5, 'dewpoint_c': 12.2, 'dewpoint_f': 54.0, 'vis_km': 16.0, 'vis_miles': 9.0, 'uv': 5.0, 'gust_mph': 15.0, 'gust_kph': 24.2}}\"}]", + "additional_kwargs": {}, + "response_metadata": {}, + "type": "tool", + "name": "tavily_search_results_json", + "id": "686b2487-f332-4e58-9508-89b3a814cd81", + "tool_call_id": "toolu_01AaNPSPzqia21v7aAKwbKYm", + "artifact": { + "query": "weather in san francisco", + "follow_up_questions": null, + "answer": null, + "images": [], + "results": [ + { + "title": "Weather in San Francisco", + "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': 1725052131, 'localtime': '2024-08-30 14:08'}, 'current': {'last_updated_epoch': 1725051600, 'last_updated': '2024-08-30 14:00', 'temp_c': 21.1, 'temp_f': 70.0, 'is_day': 1, 'condition': {'text': 'Partly cloudy', 'icon': '//cdn.weatherapi.com/weather/64x64/day/116.png', 'code': 1003}, 'wind_mph': 11.9, 'wind_kph': 19.1, 'wind_degree': 290, 'wind_dir': 'WNW', 'pressure_mb': 1018.0, 'pressure_in': 30.07, 'precip_mm': 0.0, 'precip_in': 0.0, 'humidity': 59, 'cloud': 25, 'feelslike_c': 21.1, 'feelslike_f': 70.0, 'windchill_c': 18.6, 'windchill_f': 65.5, 'heatindex_c': 18.6, 'heatindex_f': 65.5, 'dewpoint_c': 12.2, 'dewpoint_f': 54.0, 'vis_km': 16.0, 'vis_miles': 9.0, 'uv': 5.0, 'gust_mph': 15.0, 'gust_kph': 24.2}}", + "score": 0.976148, + "raw_content": null + } + ], + "response_time": 3.07 + }, + "status": "success" + }, + { + "content": [ + { + "text": "\n\nThe search results provide the current weather conditions in San Francisco. According to the data, as of 2:00 PM on August 30, 2024, the temperature in San Francisco is 70\u00b0F (21.1\u00b0C) with partly cloudy skies. The wind is blowing from the west-northwest at around 12 mph (19 km/h). The humidity is 59% and visibility is 9 miles (16 km). Overall, it looks like a nice late summer day in San Francisco with comfortable temperatures and partly sunny conditions.", + "type": "text", + "index": 0 + } + ], + "additional_kwargs": {}, + "response_metadata": { + "stop_reason": "end_turn", + "stop_sequence": null + }, + "type": "ai", + "name": null, + "id": "run-8fecc61d-3d9f-4e16-8e8a-92f702be498a", + "example": false, + "tool_calls": [], + "invalid_tool_calls": [], + "usage_metadata": { + "input_tokens": 837, + "output_tokens": 124, + "total_tokens": 961 + } + } + ] + }, + "next": [], + "tasks": [], + "metadata": { + "step": 3, + "run_id": "1ef67140-eb23-684b-8253-91d4c90bb05e", + "source": "loop", + "writes": { + "agent": { + "messages": [ + { + "id": "run-8fecc61d-3d9f-4e16-8e8a-92f702be498a", + "name": null, + "type": "ai", + "content": [ + { + "text": "\n\nThe search results provide the current weather conditions in San Francisco. According to the data, as of 2:00 PM on August 30, 2024, the temperature in San Francisco is 70\u00b0F (21.1\u00b0C) with partly cloudy skies. The wind is blowing from the west-northwest at around 12 mph (19 km/h). The humidity is 59% and visibility is 9 miles (16 km). Overall, it looks like a nice late summer day in San Francisco with comfortable temperatures and partly sunny conditions.", + "type": "text", + "index": 0 + } + ], + "example": false, + "tool_calls": [], + "usage_metadata": { + "input_tokens": 837, + "total_tokens": 961, + "output_tokens": 124 + }, + "additional_kwargs": {}, + "response_metadata": { + "stop_reason": "end_turn", + "stop_sequence": null + }, + "invalid_tool_calls": [] + } + ] + } + }, + "user_id": "", + "graph_id": "agent", + "thread_id": "5cb1e8a1-34b3-4a61-a34e-71a9799bd00d", + "created_by": "system", + "assistant_id": "fe096781-5601-53d2-b2f6-0d3403f7e9ca" + }, + "created_at": "2024-08-30T21:09:00.079909+00:00", + "checkpoint_id": "1ef67141-3ca2-6fae-8003-fe96832e57d6", + "parent_checkpoint_id": "1ef67141-2129-6b37-8002-61fc3bf69cb5" + } + +We can also just print the content of the last AIMessage: + +=== "Python" + + ```python + print(final_result['values']['messages'][-1]['content'][0]['text']) + ``` + +=== "Javascript" + + ```js + console.log(finalResult['values']['messages'][finalResult['values']['messages'].length-1]['content'][0]['text']); + ``` + +=== "CURL" + + ```bash + curl --request GET \ + --url /threads//state | jq -r '.values.messages[-1].content.[0].text' + ``` + +Output: + + The search results provide the current weather conditions in San Francisco. According to the data, as of 2:00 PM on August 30, 2024, the temperature in San Francisco is 70°F (21.1°C) with partly cloudy skies. The wind is blowing from the west-northwest at around 12 mph (19 km/h). The humidity is 59% and visibility is 9 miles (16 km). Overall, it looks like a nice late summer day in San Francisco with comfortable temperatures and partly sunny conditions. \ No newline at end of file diff --git a/docs/docs/cloud/how-tos/check_thread_status.md b/docs/docs/cloud/how-tos/check_thread_status.md index 655b918c1..5fb26c403 100644 --- a/docs/docs/cloud/how-tos/check_thread_status.md +++ b/docs/docs/cloud/how-tos/check_thread_status.md @@ -13,6 +13,7 @@ First, we need to setup our client so that we can communicate with our hosted gr ```python from langgraph_sdk import get_client client = get_client(url=) + # Using the graph deployed with the name "agent" assistant_id = "agent" thread = await client.threads.create() ``` @@ -23,7 +24,8 @@ First, we need to setup our client so that we can communicate with our hosted gr import { Client } from "@langchain/langgraph-sdk"; const client = new Client({ apiUrl: }); - const assistantId = agent; + // Using the graph deployed with the name "agent" + const assistantId = "agent"; const thread = await client.threads.create(); ``` @@ -48,7 +50,7 @@ We can use the following commands to find threads that are idle, which means tha === "Javascript" ```js - console.log(await client.threads.search({status: "idle",limit:1})); + console.log(await client.threads.search({ status: "idle", limit: 1 })); ``` === "CURL" @@ -83,7 +85,7 @@ We can use the following commands to find threads that have been interrupted in === "Javascript" ```js - console.log(await client.threads.search({status: "interrupted",limit:1})); + console.log(await client.threads.search({ status: "interrupted", limit: 1 })); ``` === "CURL" @@ -117,7 +119,7 @@ We can use the following commands to find threads that are busy, meaning they ar === "Javascript" ```js - console.log(await client.threads.search({status: "busy",limit: 1})); + console.log(await client.threads.search({ status: "busy", limit: 1 })); ``` === "CURL" @@ -183,7 +185,7 @@ The search endpoint for threads also allows you to filter on metadata, which can === "Javascript" ```js - console.log((await client.threads.search({metadata: {"foo":"bar"},limit: 1}))[0].status); + console.log((await client.threads.search({ metadata: { "foo": "bar" }, limit: 1 }))[0].status); ``` === "CURL" diff --git a/docs/docs/cloud/how-tos/configuration_cloud.md b/docs/docs/cloud/how-tos/configuration_cloud.md new file mode 100644 index 000000000..becdf802d --- /dev/null +++ b/docs/docs/cloud/how-tos/configuration_cloud.md @@ -0,0 +1,264 @@ +# How to create agents with configuration + +One of the benefits of LangGraph API is that it lets you create agents with different configurations. +This is useful when you want to: + +- Define a cognitive architecture once as a LangGraph +- Let that LangGraph be configurable across some attributes (for example, system message or LLM to use) +- Let users create agents with arbitrary configurations, save them, and then use them in the future + +In this guide we will show how to do that for the default agent we have built in. + +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: + +=== "Python" + + ```python + def call_model(state, config): + messages = state["messages"] + model_name = config.get('configurable', {}).get("model_name", "anthropic") + model = _get_model(model_name) + response = model.invoke(messages) + # We return a list, because this will get added to the existing list + return {"messages": [response]} + ``` + +=== "Javascript" + + ```js + function callModel(state: State, config: RunnableConfig) { + const messages = state.messages; + const modelName = config.configurable?.model_name ?? "anthropic"; + const model = _getModel(modelName); + const response = model.invoke(messages); + // We return a list, because this will get added to the existing list + return { messages: [response] }; + } + ``` + +We are looking inside the config for a `model_name` parameter (which defaults to `anthropic` if none is found). That means that by default we are using Anthropic as our model provider. In this example we will see an example of how to create an example agent that is configured to use OpenAI. + +First let's set up our client and thread: + +=== "Python" + + ```python + from langgraph_sdk import get_client + + client = get_client(url=) + # Select an assistant that is not configured + assistants = await client.assistants.search() + assistant = [a for a in assistants if not a["config"]][0] + ``` + +=== "Javascript" + + ```js + import { Client } from "@langchain/langgraph-sdk"; + + const client = new Client({ apiUrl: }); + // Select an assistant that is not configured + const assistants = await client.assistants.search(); + const assistant = assistants.find(a => !a.config); + ``` + +=== "CURL" + + ```bash + curl --request POST \ + --url /assistants/search \ + --header 'Content-Type: application/json' \ + --data '{ + "limit": 10, + "offset": 0 + }' | jq -c 'map(select(.config == null or .config == {})) | .[0]' + ``` + +We can now call `.get_schemas` to get schemas associated with this graph: + +=== "Python" + + ```python + schemas = await client.assistants.get_schemas( + assistant_id=assistant["assistant_id"] + ) + # There are multiple types of schemas + # We can get the `config_schema` to look at the the configurable parameters + print(schemas["config_schema"]) + ``` + +=== "Javascript" + + ```js + const schemas = await client.assistants.getSchemas( + assistant["assistant_id"] + ); + // There are multiple types of schemas + // We can get the `config_schema` to look at the the configurable parameters + console.log(schemas.config_schema); + ``` + +=== "CURL" + + ```bash + curl --request GET \ + --url /assistants//schemas | jq -r '.config_schema' + ``` + +Output: + + { + 'model_name': + { + 'title': 'Model Name', + 'enum': ['anthropic', 'openai'], + 'type': 'string' + } + } + +Now we can initialize an assistant with config: + +=== "Python" + + ```python + openai_assistant = await client.assistants.create( + # "agent" is the name of a graph we deployed + "agent", config={"configurable": {"model_name": "openai"}} + ) + + print(openai_assistant) + ``` + +=== "Javascript" + + ```js + let openAIAssistant = await client.assistants.create( + // "agent" is the name of a graph we deployed + "agent", { "configurable": { "model_name": "openai" } } + ); + + console.log(openAIAssistant); + ``` + +=== "CURL" + + ```bash + curl --request POST \ + --url /assistants \ + --header 'Content-Type: application/json' \ + --data '{"graph_id":"agent","config":{"configurable":{"model_name":"open_ai"}}}' + ``` + +Output: + + { + "assistant_id": "62e209ca-9154-432a-b9e9-2d75c7a9219b", + "graph_id": "agent", + "created_at": "2024-08-31T03:09:10.230718+00:00", + "updated_at": "2024-08-31T03:09:10.230718+00:00", + "config": { + "configurable": { + "model_name": "open_ai" + } + }, + "metadata": {} + } + +We can verify the config is indeed taking effect: + +=== "Python" + + ```python + thread = await client.threads.create() + input = {"messages": [{"role": "user", "content": "who made you?"}]} + async for event in client.runs.stream( + thread["thread_id"], + openai_assistant["assistant_id"], + input=input, + stream_mode="updates", + ): + print(f"Receiving event of type: {event.event}") + print(event.data) + print("\n\n") + ``` + +=== "Javascript" + + ```js + const thread = await client.threads.create(); + let input = { "messages": [{ "role": "user", "content": "who made you?" }] }; + + const streamResponse = client.runs.stream( + thread["thread_id"], + openAIAssistant["assistant_id"], + { + input, + streamMode: "updates" + } + ); + + for await (const event of streamResponse) { + console.log(`Receiving event of type: ${event.event}`); + console.log(event.data); + console.log("\n\n"); + } + ``` + +=== "CURL" + + ```bash + thread_id=$(curl --request POST \ + --url /threads \ + --header 'Content-Type: application/json' \ + --data '{}' | jq -r '.thread_id') && \ + curl --request POST \ + --url "/threads/${thread_id}/runs/stream" \ + --header 'Content-Type: application/json' \ + --data '{ + "assistant_id": , + "input": { + "messages": [ + { + "role": "human", + "content": "who made you?" + } + ] + }, + "stream_mode": [ + "updates" + ] + }' | \ + sed 's/\r$//' | \ + awk ' + /^event:/ { + if (data_content != "") { + print data_content "\n" + } + sub(/^event: /, "Receiving event of type: ", $0) + printf "%s...\n", $0 + data_content = "" + } + /^data:/ { + sub(/^data: /, "", $0) + data_content = $0 + } + END { + if (data_content != "") { + print data_content "\n\n" + } + } + ' + ``` + +Output: + + Receiving event of type: metadata + {'run_id': '1ef6746e-5893-67b1-978a-0f1cd4060e16'} + + + + Receiving event of type: updates + {'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', 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_157b3831f5'}, 'type': 'ai', 'name': None, 'id': 'run-e1a6b25c-8416-41f2-9981-f9cfe043f414', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]}} + + + diff --git a/docs/docs/cloud/how-tos/copy_threads.md b/docs/docs/cloud/how-tos/copy_threads.md index 9a27282b2..22586ed4d 100644 --- a/docs/docs/cloud/how-tos/copy_threads.md +++ b/docs/docs/cloud/how-tos/copy_threads.md @@ -24,8 +24,8 @@ First, we need to setup our client so that we can communicate with our hosted gr ```js import { Client } from "@langchain/langgraph-sdk"; - const client = new Client({ apiUrl:"" }); - const assistantId = agent; + const client = new Client({ apiUrl: "" }); + const assistantId = "agent"; const thread = await client.threads.create(); ``` @@ -92,21 +92,21 @@ We can verify that the history from the prior thread did indeed copy over correc ```js function removeThreadId(d) { - if (d.metadata && d.metadata.thread_id) { - delete d.metadata.thread_id; - } - return d; + if (d.metadata && d.metadata.thread_id) { + delete d.metadata.thread_id; + } + return d; } // Assuming `client.threads.getHistory(threadId)` is an async function that returns a list of dicts async function compareThreadHistories(threadId, copiedThreadId) { - const originalThreadHistory = (await client.threads.getHistory(threadId)).map(removeThreadId); - const copiedThreadHistory = (await client.threads.getHistory(copiedThreadId)).map(removeThreadId); + const originalThreadHistory = (await client.threads.getHistory(threadId)).map(removeThreadId); + const copiedThreadHistory = (await client.threads.getHistory(copiedThreadId)).map(removeThreadId); - // Compare the two histories - console.assert(JSON.stringify(originalThreadHistory) === JSON.stringify(copiedThreadHistory)) - // if we made it here the assertion passed! - console.log("The histories are the same."); + // Compare the two histories + console.assert(JSON.stringify(originalThreadHistory) === JSON.stringify(copiedThreadHistory)); + // if we made it here the assertion passed! + console.log("The histories are the same."); } // Example usage diff --git a/docs/docs/cloud/how-tos/cron_jobs.md b/docs/docs/cloud/how-tos/cron_jobs.md new file mode 100644 index 000000000..f787c8b9c --- /dev/null +++ b/docs/docs/cloud/how-tos/cron_jobs.md @@ -0,0 +1,184 @@ +# Cron Jobs + +Sometimes you don't want to run your graph based on user interaction, but rather you would like to schedule your graph to run on a schedule - for example if you wish for your graph to compose and send out a weekly email of to-dos for your team. LangGraph Cloud allows you to do this without having to write your own script by using the `Crons` client. To schedule a graph job, you need to pass a [cron expression](https://crontab.cronhub.io/) to inform the client when you want to run the graph. `Cron` jobs are run in the background and do not interfere with normal invocations of the graph. + +## Setup + +First, let's setup our SDK client, assistant, and thread: + +=== "Python" + + ```python + from langgraph_sdk import get_client + + client = get_client(url=) + # Using the graph deployed with the name "agent" + assistant_id = "agent" + # create thread + thread = await client.threads.create() + print(thread) + ``` + +=== "Javascript" + + ```js + import { Client } from "@langchain/langgraph-sdk"; + + const client = new Client({ apiUrl: }); + // Using the graph deployed with the name "agent" + const assistantId = "agent"; + // create thread + const thread = await client.threads.create(); + console.log(thread); + ``` + +=== "CURL" + + ```bash + curl --request POST \ + --url /assistants/search \ + --header 'Content-Type: application/json' \ + --data '{ + "limit": 10, + "offset": 0 + }' | jq -c 'map(select(.config == null or .config == {})) | .[0].graph_id' && \ + curl --request POST \ + --url /threads \ + --header 'Content-Type: application/json' \ + --data '{}' + ``` + +Output: + + { + 'thread_id': '9dde5490-2b67-47c8-aa14-4bfec88af217', + 'created_at': '2024-08-30T23:07:38.242730+00:00', + 'updated_at': '2024-08-30T23:07:38.242730+00:00', + 'metadata': {}, + 'status': 'idle', + 'config': {}, + 'values': None + } + +## Cron job on a thread + +To create a cron job associated with a specific thread, you can write: + + +=== "Python" + + ```python + # This schedules a job to run at 15:27 (3:27PM) every day + cron_job = await client.crons.create_for_thread( + thread["thread_id"], + assistant_id, + schedule="27 15 * * *", + input={"messages": [{"role": "user", "content": "What time is it?"}]}, + ) + ``` + +=== "Javascript" + + ```js + // This schedules a job to run at 15:27 (3:27PM) every day + const cronJob = await client.crons.create_for_thread( + thread["thread_id"], + assistantId, + { + schedule: "27 15 * * *", + input: { messages: [{ role: "user", content: "What time is it?" }] } + } + ); + ``` + +=== "CURL" + + ```bash + curl --request POST \ + --url /threads//runs/crons \ + --header 'Content-Type: application/json' \ + --data '{ + "assistant_id": , + }' + ``` + +Note that it is **very** important to delete `Cron` jobs that are no longer useful. Otherwise you could rack up unwanted API charges to the LLM! You can delete a `Cron` job using the following code: + +=== "Python" + + ```python + await client.crons.delete(cron_job["cron_id"]) + ``` + +=== "Javascript" + + ```js + await client.crons.delete(cronJob["cron_id"]); + ``` + +=== "CURL" + + ```bash + curl --request DELETE \ + --url /runs/crons/ + ``` + +## Cron job stateless + +You can also create stateless cron jobs by using the following code: + +=== "Python" + + ```python + # This schedules a job to run at 15:27 (3:27PM) every day + cron_job_stateless = await client.crons.create( + assistant_id, + schedule="27 15 * * *", + input={"messages": [{"role": "user", "content": "What time is it?"}]}, + ) + ``` + +=== "Javascript" + + ```js + // This schedules a job to run at 15:27 (3:27PM) every day + const cronJobStateless = await client.crons.create( + assistantId, + { + schedule: "27 15 * * *", + input: { messages: [{ role: "user", content: "What time is it?" }] } + } + ); + ``` + +=== "CURL" + + ```bash + curl --request POST \ + --url /runs/crons \ + --header 'Content-Type: application/json' \ + --data '{ + "assistant_id": , + }' + ``` + +Again, remember to delete your job once you are done with it! + +=== "Python" + + ```python + await client.crons.delete(cron_job_stateless["cron_id"]) + ``` + +=== "Javascript" + + ```js + await client.crons.delete(cronJobStateless["cron_id"]); + ``` + +=== "CURL" + + ```bash + curl --request DELETE \ + --url /runs/crons/ + ``` diff --git a/docs/docs/cloud/how-tos/enqueue_concurrent.md b/docs/docs/cloud/how-tos/enqueue_concurrent.md index 17c4ea0f5..5172b8ac6 100644 --- a/docs/docs/cloud/how-tos/enqueue_concurrent.md +++ b/docs/docs/cloud/how-tos/enqueue_concurrent.md @@ -5,20 +5,44 @@ This guide assumes knowledge of what double-texting is, which you can learn abou The guide covers the `enqueue` option for double texting, which adds the interruptions to a queue and executes them in the order they are received by the client. Below is a quick example of using the `enqueue` option. -First, we will define a quick helper function for printing out JS model outputs (you can skip this if using Python): +First, we will define a quick helper function for printing out JS and CURL model outputs (you can skip this if using Python): -```js -function prettyPrint(m) { - const padded = " " + m['type'] + " "; - const sepLen = Math.floor((80 - padded.length) / 2); - const sep = "=".repeat(sepLen); - const secondSep = sep + (padded.length % 2 ? "=" : ""); - - console.log(`${sep}${padded}${secondSep}`); - console.log("\n\n"); - console.log(m.content); -} -``` +=== "Javascript" + + ```js + function prettyPrint(m) { + const padded = " " + m['type'] + " "; + const sepLen = Math.floor((80 - padded.length) / 2); + const sep = "=".repeat(sepLen); + const secondSep = sep + (padded.length % 2 ? "=" : ""); + + console.log(`${sep}${padded}${secondSep}`); + console.log("\n\n"); + console.log(m.content); + } + ``` + +=== "CURL" + + ```bash + # PLACE THIS IN A FILE CALLED pretty_print.sh + pretty_print() { + local type="$1" + local content="$2" + local padded=" $type " + local total_width=80 + local sep_len=$(( (total_width - ${#padded}) / 2 )) + local sep=$(printf '=%.0s' $(eval "echo {1.."${sep_len}"}")) + local second_sep=$sep + if (( (total_width - ${#padded}) % 2 )); then + second_sep="${second_sep}=" + fi + + echo "${sep}${padded}${second_sep}" + echo + echo "$content" + } + ``` Then, let's import our required packages and instantiate our client, assistant, and thread. @@ -32,6 +56,7 @@ Then, let's import our required packages and instantiate our client, assistant, from langgraph_sdk import get_client client = get_client(url=) + # Using the graph deployed with the name "agent" assistant_id = "agent" thread = await client.threads.create() ``` @@ -43,9 +68,18 @@ Then, let's import our required packages and instantiate our client, assistant, const client = new Client({ apiUrl: }); + // Using the graph deployed with the name "agent" const assistantId = "agent"; const thread = await client.threads.create(); ``` + +=== "CURL" + + ```bash + curl --request POST \ + --url /threads \ + --header 'Content-Type: application/json' + ``` Now let's start two runs, with the second interrupting the first one with a multitask strategy of "enqueue": @@ -82,6 +116,25 @@ Now let's start two runs, with the second interrupting the first one with a mult ) ``` +=== "CURL" + + ```bash + curl --request POST \ + --url >/threads//runs \ + --header 'Content-Type: application/json' \ + --data "{ + \"assistant_id\": \"agent\", + \"input\": {\"messages\": [{\"role\": \"human\", \"content\": \"what\'s the weather in sf?\"}]}, + }" && curl --request POST \ + --url >/threads//runs \ + --header 'Content-Type: application/json' \ + --data "{ + \"assistant_id\": \"agent\", + \"input\": {\"messages\": [{\"role\": \"human\", \"content\": \"what\'s the weather in nyc?\"}]}, + \"multitask_strategy\": \"enqueue\" + }" + ``` + Verify that the thread has data from both runs: === "Python" @@ -108,12 +161,25 @@ Verify that the thread has data from both runs: } ``` +=== "CURL" + + ```bash + source pretty_print.sh && curl --request GET \ + --url /threads//runs//join && \ + curl --request GET --url /threads//state | \ + jq -c '.values.messages[]' | while read -r element; do + type=$(echo "$element" | jq -r '.type') + content=$(echo "$element" | jq -r '.content | if type == "array" then tostring else . end') + pretty_print "$type" "$content" + done + ``` + Output: - ================================ Human Message ================================= + ================================ Human Message ================================= what's the weather in sf? - ================================== Ai Message ================================== + ================================== Ai Message ================================== [{'id': 'toolu_01Dez1sJre4oA2Y7NsKJV6VT', 'input': {'query': 'weather in san francisco'}, 'name': 'tavily_search_results_json', 'type': 'tool_use'}] Tool Calls: @@ -121,11 +187,11 @@ Output: Call ID: toolu_01Dez1sJre4oA2Y7NsKJV6VT Args: query: weather in san francisco - ================================= Tool Message ================================= + ================================= Tool Message ================================= Name: tavily_search_results_json [{"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."}] - ================================== Ai Message ================================== + ================================== Ai Message ================================== According to AccuWeather, the current weather conditions in San Francisco are: @@ -145,10 +211,10 @@ Output: Sunday: Partly sunny, high of 61°F (16°C) 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. - ================================ Human Message ================================= + ================================ Human Message ================================= what's the weather in nyc? - ================================== Ai Message ================================== + ================================== Ai Message ================================== [{'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'}] Tool Calls: @@ -156,11 +222,11 @@ Output: Call ID: toolu_01FFft5Sx9oS6AdVJuRWWcGp Args: query: weather in new york city - ================================= Tool Message ================================= + ================================= Tool Message ================================= Name: tavily_search_results_json [{"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}}"}] - ================================== Ai Message ================================== + ================================== Ai Message ================================== According to the weather data from WeatherAPI: diff --git a/docs/docs/cloud/how-tos/human_in_the_loop_breakpoint.md b/docs/docs/cloud/how-tos/human_in_the_loop_breakpoint.md index 56c1931e2..08eaa0f3d 100644 --- a/docs/docs/cloud/how-tos/human_in_the_loop_breakpoint.md +++ b/docs/docs/cloud/how-tos/human_in_the_loop_breakpoint.md @@ -22,6 +22,7 @@ In this how-to we use a simple ReAct style hosted graph (you can see the full co ```python from langgraph_sdk import get_client client = get_client(url=) + # Using the graph deployed with the name "agent" assistant_id = "agent" thread = await client.threads.create() ``` @@ -32,7 +33,8 @@ In this how-to we use a simple ReAct style hosted graph (you can see the full co import { Client } from "@langchain/langgraph-sdk"; const client = new Client({ apiUrl: }); - const assistantId = "agent" + // Using the graph deployed with the name "agent" + const assistantId = "agent"; const thread = await client.threads.create(); ``` @@ -73,7 +75,7 @@ And, now let's compile it with a breakpoint before the tool node: === "Javascript" ```js - const input = { "messages": [{ "role": "human", "content": "what's the weather in sf"}] } + const input = { messages: [{ role: "human", content: "what's the weather in sf" }] }; const streamResponse = client.runs.stream( thread["thread_id"], @@ -81,9 +83,10 @@ And, now let's compile it with a breakpoint before the tool node: { input: input, streamMode: "updates", - interruptBefore: ["action"], + interruptBefore: ["action"] } ); + for await (const chunk of streamResponse) { console.log(`Receiving new event of type: ${chunk.event}...`); console.log(chunk.data); diff --git a/docs/docs/cloud/how-tos/human_in_the_loop_edit_state.md b/docs/docs/cloud/how-tos/human_in_the_loop_edit_state.md index 668d826a8..927df7164 100644 --- a/docs/docs/cloud/how-tos/human_in_the_loop_edit_state.md +++ b/docs/docs/cloud/how-tos/human_in_the_loop_edit_state.md @@ -18,6 +18,7 @@ First, we need to setup our client so that we can communicate with our hosted gr ```python from langgraph_sdk import get_client client = get_client(url=) + # Using the graph deployed with the name "agent" assistant_id = "agent" thread = await client.threads.create() ``` @@ -28,6 +29,7 @@ First, we need to setup our client so that we can communicate with our hosted gr import { Client } from "@langchain/langgraph-sdk"; const client = new Client({ apiUrl: }); + // Using the graph deployed with the name "agent" const assistantId = "agent"; const thread = await client.threads.create(); ``` @@ -65,7 +67,7 @@ Now let's invoke our graph, making sure to interrupt before the `action` node. === "Javascript" ```js - const input = {"messages": [{ "role": "human", "content": "search for weather in SF"}] } + const input = { messages: [{ role: "human", content: "search for weather in SF" }] }; const streamResponse = client.runs.stream( thread["thread_id"], @@ -76,6 +78,7 @@ Now let's invoke our graph, making sure to interrupt before the `action` node. interruptBefore: ["action"], } ); + for await (const chunk of streamResponse) { if (chunk.data && chunk.event !== "metadata") { console.log(chunk.data); @@ -154,15 +157,15 @@ Now, let's assume we actually meant to search for the weather in Sidi Frej (anot === "Javascript" ```js - // First, lets get the current state - const currentState = await client.threads.getState(thread['thread_id']); + // First, let's get the current state + const currentState = await client.threads.getState(thread["thread_id"]); // Let's now get the last message in the state // This is the one with the tool calls that we want to update - let lastMessage = currentState['values']['messages'][-1]; + let lastMessage = currentState.values.messages.slice(-1)[0]; // Let's now update the args for that tool call - lastMessage['tool_calls'][0]['args'] = {'query': 'current weather in Sidi Frej'}; + lastMessage.tool_calls[0].args = { query: "current weather in Sidi Frej" }; // Let's now call `update_state` to pass in this message in the `messages` key // This will get treated as any other update to the state @@ -170,7 +173,7 @@ Now, let's assume we actually meant to search for the weather in Sidi Frej (anot // That reducer function will use the ID of the message to update it // It's important that it has the right ID! Otherwise it would get appended // as a new message - await client.threads.updateState(thread['thread_id'], {values:{"messages": lastMessage}}); + await client.threads.updateState(thread["thread_id"], { values: { messages: lastMessage } }); ``` === "CURL" @@ -220,6 +223,7 @@ Now we can resume our graph run but with the updated state: streamMode: "updates", } ); + for await (const chunk of streamResponse) { if (chunk.data && chunk.event !== "metadata") { console.log(chunk.data); diff --git a/docs/docs/cloud/how-tos/human_in_the_loop_review_tool_calls.md b/docs/docs/cloud/how-tos/human_in_the_loop_review_tool_calls.md index e9a698181..69f247807 100644 --- a/docs/docs/cloud/how-tos/human_in_the_loop_review_tool_calls.md +++ b/docs/docs/cloud/how-tos/human_in_the_loop_review_tool_calls.md @@ -29,6 +29,7 @@ First, we need to setup our client so that we can communicate with our hosted gr ```python from langgraph_sdk import get_client client = get_client(url=) + # Using the graph deployed with the name "agent" assistant_id = "agent" thread = await client.threads.create() ``` @@ -39,10 +40,19 @@ First, we need to setup our client so that we can communicate with our hosted gr import { Client } from "@langchain/langgraph-sdk"; const client = new Client({ apiUrl: }); + // Using the graph deployed with the name "agent" const assistantId = "agent"; const thread = await client.threads.create(); ``` +=== "CURL" + + ```bash + curl --request POST \ + --url /threads \ + --header 'Content-Type: application/json' + ``` + ## Example with no review Let's look at an example when no review is required (because no tools are called) @@ -66,7 +76,7 @@ Let's look at an example when no review is required (because no tools are called === "Javascript" ```js - const input = {"messages": [{ "role": "human", "content": "hi!"}] } + const input = { "messages": [{ "role": "human", "content": "hi!" }] }; const streamResponse = client.runs.stream( thread["thread_id"], @@ -77,6 +87,7 @@ Let's look at an example when no review is required (because no tools are called interruptBefore: ["action"], } ); + for await (const chunk of streamResponse) { if (chunk.data && chunk.event !== "metadata") { console.log(chunk.data); @@ -84,6 +95,42 @@ Let's look at an example when no review is required (because no tools are called } ``` +=== "CURL" + + ```bash + curl --request POST \ + --url /threads//runs/stream \ + --header 'Content-Type: application/json' \ + --data "{ + \"assistant_id\": \"agent\", + \"input\": {\"messages\": [{\"role\": \"human\", \"content\": \"hi!\"}]}, + \"stream_mode\": [ + \"updates\" + ], + \"interrupt_before\": [\"action\"] + }" | \ + sed 's/\r$//' | \ + awk ' + /^event:/ { + if (data_content != "" && event_type != "metadata") { + print data_content "\n" + } + sub(/^event: /, "", $0) + event_type = $0 + data_content = "" + } + /^data:/ { + sub(/^data: /, "", $0) + data_content = $0 + } + END { + if (data_content != "" && event_type != "metadata") { + print data_content "\n" + } + } + ' + ``` + Output: {'messages': [{'content': 'hi!', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '39c51f14-2d5c-4690-883a-d940854b1845', 'example': False}]} @@ -108,6 +155,13 @@ If we check the state, we can see that it is finished console.log(state.next); ``` +=== "CURL" + + ```bash + curl --request GET \ + --url /threads//state | jq -c '.next' + ``` + Output: [] @@ -125,7 +179,6 @@ Let's now look at what it looks like to approve a tool call. Note that we don't thread["thread_id"], "agent", input=input, - stream_mode="values", ): if chunk.data and chunk.event != "metadata": print(chunk.data) @@ -134,16 +187,16 @@ Let's now look at what it looks like to approve a tool call. Note that we don't === "Javascript" ```js - const input = {"messages": [{"role": "user", "content": "what's the weather in sf?"}]} + const input = { "messages": [{ "role": "user", "content": "what's the weather in sf?" }] }; const streamResponse = client.runs.stream( thread["thread_id"], assistantId, { input: input, - streamMode: "values", } ); + for await (const chunk of streamResponse) { if (chunk.data && chunk.event !== "metadata") { console.log(chunk.data); @@ -151,6 +204,38 @@ Let's now look at what it looks like to approve a tool call. Note that we don't } ``` +=== "CURL" + + ```bash + curl --request POST \ + --url /threads//runs/stream \ + --header 'Content-Type: application/json' \ + --data "{ + \"assistant_id\": \"agent\", + \"input\": {\"messages\": [{\"role\": \"human\", \"content\": \"what's the weather in sf?\"}]} + }" | \ + sed 's/\r$//' | \ + awk ' + /^event:/ { + if (data_content != "" && event_type != "metadata") { + print data_content "\n" + } + sub(/^event: /, "", $0) + event_type = $0 + data_content = "" + } + /^data:/ { + sub(/^data: /, "", $0) + data_content = $0 + } + END { + if (data_content != "" && event_type != "metadata") { + print data_content "\n" + } + } + ' + ``` + Output: {'messages': [{'content': "what's the weather in sf?", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '54e19d6e-89fa-44fb-b92c-12e7dd4ddf08', 'example': False}]} @@ -175,6 +260,13 @@ If we now check, we can see that it is waiting on human review: console.log(state.next); ``` +=== "CURL" + + ```bash + curl --request GET \ + --url /threads//state | jq -c '.next' + ``` + Output: ['human_review_node'] @@ -201,10 +293,11 @@ To approve the tool call, we can just continue the thread with no edits. To do t thread["thread_id"], assistantId, { - input: undefined, + input: null, streamMode: "values", } ); + for await (const chunk of streamResponse) { if (chunk.data && chunk.event !== "metadata") { console.log(chunk.data); @@ -212,6 +305,37 @@ To approve the tool call, we can just continue the thread with no edits. To do t } ``` +=== "CURL" + + ```bash + curl --request POST \ + --url /threads//runs/stream \ + --header 'Content-Type: application/json' \ + --data "{ + \"assistant_id\": \"agent\" + }" | \ + sed 's/\r$//' | \ + awk ' + /^event:/ { + if (data_content != "" && event_type != "metadata") { + print data_content "\n" + } + sub(/^event: /, "", $0) + event_type = $0 + data_content = "" + } + /^data:/ { + sub(/^data: /, "", $0) + data_content = $0 + } + END { + if (data_content != "" && event_type != "metadata") { + print data_content "\n" + } + } + ' + ``` + Output: {'messages': [{'content': "what's the weather in sf?", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '54e19d6e-89fa-44fb-b92c-12e7dd4ddf08', 'example': False}, {'content': [{'text': "Certainly! I can help you check the weather in San Francisco. To get this information, I'll use the weather search function. Let me do that for you right away.", 'type': 'text', 'index': 0}, {'id': 'toolu_015yrR3GMDXe6X8m2p9CsEDN', 'input': {}, 'name': 'weather_search', 'type': 'tool_use', 'index': 1, 'partial_json': '{"city": "San Francisco"}'}], 'additional_kwargs': {}, 'response_metadata': {'stop_reason': 'tool_use', 'stop_sequence': None}, 'type': 'ai', 'name': None, 'id': 'run-45a6b6c3-ac69-42a4-8957-d982203d6392', 'example': False, 'tool_calls': [{'name': 'weather_search', 'args': {'city': 'San Francisco'}, 'id': 'toolu_015yrR3GMDXe6X8m2p9CsEDN', 'type': 'tool_call'}], 'invalid_tool_calls': [], 'usage_metadata': {'input_tokens': 360, 'output_tokens': 90, 'total_tokens': 450}}, {'content': 'Sunny!', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': 'weather_search', 'id': '826cd0f2-9cc6-46f0-b7df-daa6a05d13d2', 'tool_call_id': 'toolu_015yrR3GMDXe6X8m2p9CsEDN', 'artifact': None, 'status': 'success'}]} @@ -239,7 +363,7 @@ Let's now say we want to edit the tool call. E.g. change some of the parameters === "Javascript" ```js - const input = {"messages": [{"role": "user", "content": "what's the weather in sf?"}]} + const input = { "messages": [{ "role": "user", "content": "what's the weather in sf?" }] }; const streamResponse = client.runs.stream( thread["thread_id"], @@ -249,6 +373,7 @@ Let's now say we want to edit the tool call. E.g. change some of the parameters streamMode: "values", } ); + for await (const chunk of streamResponse) { if (chunk.data && chunk.event !== "metadata") { console.log(chunk.data); @@ -256,6 +381,38 @@ Let's now say we want to edit the tool call. E.g. change some of the parameters } ``` +=== "CURL" + + ```bash + curl --request POST \ + --url /threads//runs/stream \ + --header 'Content-Type: application/json' \ + --data "{ + \"assistant_id\": \"agent\", + \"input\": {\"messages\": [{\"role\": \"human\", \"content\": \"what's the weather in sf?\"}]} + }" | \ + sed 's/\r$//' | \ + awk ' + /^event:/ { + if (data_content != "" && event_type != "metadata") { + print data_content "\n" + } + sub(/^event: /, "", $0) + event_type = $0 + data_content = "" + } + /^data:/ { + sub(/^data: /, "", $0) + data_content = $0 + } + END { + if (data_content != "" && event_type != "metadata") { + print data_content "\n" + } + } + ' + ``` + Output: {'messages': [{'content': "what's the weather in sf?", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': 'cec11391-84da-464b-bd2a-bd4f0d93b9ee', 'example': False}]} @@ -310,7 +467,6 @@ To do this, we first need to update the state. We can do this by passing a messa thread["thread_id"], "agent", input=None, - stream_mode="values", ): if chunk.data and chunk.event != "metadata": print(chunk.data) @@ -332,43 +488,93 @@ To do this, we first need to update the state. We can do this by passing a messa // Construct a replacement tool call const newMessage = { - role: "assistant", - content: currentContent, - tool_calls: [ - { - id: toolCallId, - name: "weather_search", - args: { city: "San Francisco, USA" } - } - ], - // Ensure the ID is the same as the message you're replacing - id: currentId + role: "assistant", + content: currentContent, + tool_calls: [ + { + id: toolCallId, + name: "weather_search", + args: { city: "San Francisco, USA" } + } + ], + // Ensure the ID is the same as the message you're replacing + id: currentId }; await client.threads.updateState( - thread.thread_id, // Thread ID - { + thread.thread_id, // Thread ID + { values: { "messages": [newMessage] }, // Updated message asNode: "human_review_node" - } // Acting as human_review_node + } // Acting as human_review_node ); console.log("\nResuming Execution"); // Continue executing from here const streamResponseResumed = client.runs.stream( - thread["thread_id"], - assistantId, - { - input: undefined, - streamMode: "values", - interruptBefore: ["action"], - } + thread["thread_id"], + assistantId, + { + input: null, + } ); + for await (const chunk of streamResponseResumed) { - if (chunk.data && chunk.event !== "metadata") { + if (chunk.data && chunk.event !== "metadata") { console.log(chunk.data); + } } + ``` + +=== "CURL" + + ```bash + curl --request POST \ + --url /threads//state \ + --header 'Content-Type: application/json' \ + --data "{ + \"values\": { \"messages\": [$(curl --request GET \ + --url /threads//state | + jq -c '{ + role: "assistant", + content: .values.messages[-1].content, + tool_calls: [ + { + id: .values.messages[-1].tool_calls[0].id, + name: "weather_search", + args: { city: "San Francisco, USA" } + } + ], + id: .values.messages[-1].id + }') + ]}, + \"as_node\": \"human_review_node\" + }" && echo "Resuming Execution" && curl --request POST \ + --url /threads//runs/stream \ + --header 'Content-Type: application/json' \ + --data '{ + "assistant_id": "agent" + }' | \ + sed 's/\r$//' | \ + awk ' + /^event:/ { + if (data_content != "" && event_type != "metadata") { + print data_content "\n" + } + sub(/^event: /, "", $0) + event_type = $0 + data_content = "" } + /^data:/ { + sub(/^data: /, "", $0) + data_content = $0 + } + END { + if (data_content != "" && event_type != "metadata") { + print data_content "\n" + } + } + ' ``` Output: @@ -404,7 +610,6 @@ For this example we will just add a single tool call representing the feedback. thread["thread_id"], "agent", input=input, - stream_mode="values", ): if chunk.data and chunk.event != "metadata": print(chunk.data) @@ -413,16 +618,16 @@ For this example we will just add a single tool call representing the feedback. === "Javascript" ```js - const input = {"messages": [{"role": "user", "content": "what's the weather in sf?"}]} + const input = { "messages": [{ "role": "user", "content": "what's the weather in sf?" }] }; const streamResponse = client.runs.stream( thread["thread_id"], assistantId, { input: input, - streamMode: "values", } ); + for await (const chunk of streamResponse) { if (chunk.data && chunk.event !== "metadata") { console.log(chunk.data); @@ -430,6 +635,38 @@ For this example we will just add a single tool call representing the feedback. } ``` +=== "CURL" + + ```bash + curl --request POST \ + --url /threads//runs/stream \ + --header 'Content-Type: application/json' \ + --data "{ + \"assistant_id\": \"agent\", + \"input\": {\"messages\": [{\"role\": \"human\", \"content\": \"what's the weather in sf?\"}]} + }" | \ + sed 's/\r$//' | \ + awk ' + /^event:/ { + if (data_content != "" && event_type != "metadata") { + print data_content "\n" + } + sub(/^event: /, "", $0) + event_type = $0 + data_content = "" + } + /^data:/ { + sub(/^data: /, "", $0) + data_content = $0 + } + END { + if (data_content != "" && event_type != "metadata") { + print data_content "\n" + } + } + ' + ``` + Output: {'messages': [{'content': "what's the weather in sf?", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': 'c80f13d0-674d-4233-b6a0-3940509d3cf3', 'example': False}]} @@ -493,38 +730,85 @@ To do this, we first need to update the state. We can do this by passing a messa // Construct a replacement tool call const newMessage = { - role: "tool", - content: "User requested changes: pass in the country as well", - name: "weather_search", - tool_call_id: toolCallId, + role: "tool", + content: "User requested changes: pass in the country as well", + name: "weather_search", + tool_call_id: toolCallId, }; await client.threads.updateState( - thread.thread_id, // Thread ID - { + thread.thread_id, // Thread ID + { values: { "messages": [newMessage] }, // Updated message asNode: "human_review_node" - } // Acting as human_review_node + } // Acting as human_review_node ); console.log("\nResuming Execution"); // Continue executing from here const streamResponseEdited = client.runs.stream( - thread["thread_id"], - assistantId, - { - input: undefined, + thread["thread_id"], + assistantId, + { + input: null, streamMode: "values", interruptBefore: ["action"], - } + } ); + for await (const chunk of streamResponseEdited) { - if (chunk.data && chunk.event !== "metadata") { + if (chunk.data && chunk.event !== "metadata") { console.log(chunk.data); - } + } } ``` +=== "CURL" + + ```bash + curl --request POST \ + --url /threads//state \ + --header 'Content-Type: application/json' \ + --data "{ + \"values\": { \"messages\": [$(curl --request GET \ + --url /threads//state | + jq -c '{ + role: "tool", + content: "User requested changes: pass in the country as well", + name: "get_weather", + tool_call_id: .values.messages[-1].id.tool_calls[0].id + }') + ]}, + \"as_node\": \"human_review_node\" + }" && echo "Resuming Execution" && curl --request POST \ + --url /threads//runs/stream \ + --header 'Content-Type: application/json' \ + --data '{ + "assistant_id": "agent" + }' | \ + sed 's/\r$//' | \ + awk ' + /^event:/ { + if (data_content != "" && event_type != "metadata") { + print data_content "\n" + } + sub(/^event: /, "", $0) + event_type = $0 + data_content = "" + } + /^data:/ { + sub(/^data: /, "", $0) + data_content = $0 + } + END { + if (data_content != "" && event_type != "metadata") { + print data_content "\n" + } + } + ' + ``` + + Output: Current State: @@ -545,7 +829,6 @@ We can see that we now get to another breakpoint - because it went back to the m thread["thread_id"], "agent", input=None, - stream_mode="values", ): if chunk.data and chunk.event != "metadata": print(chunk.data) @@ -558,10 +841,10 @@ We can see that we now get to another breakpoint - because it went back to the m thread["thread_id"], assistantId, { - input: undefined, - streamMode: "values", + input: null, } ); + for await (const chunk of streamResponseResumed) { if (chunk.data && chunk.event !== "metadata") { console.log(chunk.data); @@ -569,6 +852,37 @@ We can see that we now get to another breakpoint - because it went back to the m } ``` +=== "CURL" + + ```bash + curl --request POST \ + --url /threads//runs/stream \ + --header 'Content-Type: application/json' \ + --data "{ + \"assistant_id\": \"agent\" + }" | \ + sed 's/\r$//' | \ + awk ' + /^event:/ { + if (data_content != "" && event_type != "metadata") { + print data_content "\n" + } + sub(/^event: /, "", $0) + event_type = $0 + data_content = "" + } + /^data:/ { + sub(/^data: /, "", $0) + data_content = $0 + } + END { + if (data_content != "" && event_type != "metadata") { + print data_content "\n" + } + } + ' + ``` + Output: {'messages': [{'content': "what's the weather in sf?", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '3b2bbc38-d11b-49eb-80c0-c24a40dab5a8', 'example': False}, {'content': [{'text': 'To get the weather information for San Francisco, I can use the weather_search function. Let me do that for you.', 'type': 'text', 'index': 0}, {'id': 'toolu_01NNw18j57GEGPZvsa9f1wvX', 'input': {}, 'name': 'weather_search', 'type': 'tool_use', 'index': 1, 'partial_json': '{"city": "San Francisco"}'}], 'additional_kwargs': {}, 'response_metadata': {'stop_reason': 'tool_use', 'stop_sequence': None}, 'type': 'ai', 'name': None, 'id': 'run-c5a50900-abf5-4885-9cdb-da2bf0d892ac', 'example': False, 'tool_calls': [{'name': 'weather_search', 'args': {'city': 'San Francisco'}, 'id': 'toolu_01NNw18j57GEGPZvsa9f1wvX', 'type': 'tool_call'}], 'invalid_tool_calls': [], 'usage_metadata': {'input_tokens': 360, 'output_tokens': 80, 'total_tokens': 440}}, {'content': 'User requested changes: pass in the country as well', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': 'weather_search', 'id': '787288be-213c-4fd3-8503-4a009bdb1b00', 'tool_call_id': 'toolu_01NNw18j57GEGPZvsa9f1wvX', 'artifact': None, 'status': 'success'}, {'content': [{'text': '\n\nI apologize for the oversight. It seems the function requires additional information. Let me try again with a more specific request.', 'type': 'text', 'index': 0}, {'id': 'toolu_01YAbLBoKozJyRQnB8LUMpXC', 'input': {}, 'name': 'weather_search', 'type': 'tool_use', 'index': 1, 'partial_json': '{"city": "San Francisco, USA"}'}], 'additional_kwargs': {}, 'response_metadata': {'stop_reason': 'tool_use', 'stop_sequence': None}, 'type': 'ai', 'name': None, 'id': 'run-5c355a56-cfe3-4046-b49f-f5b09fc397ef', 'example': False, 'tool_calls': [{'name': 'weather_search', 'args': {'city': 'San Francisco, USA'}, 'id': 'toolu_01YAbLBoKozJyRQnB8LUMpXC', 'type': 'tool_call'}], 'invalid_tool_calls': [], 'usage_metadata': {'input_tokens': 461, 'output_tokens': 83, 'total_tokens': 544}}, {'content': 'Sunny!', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': 'weather_search', 'id': '3b857482-bca2-4a73-a9ab-1f35a3e43e5f', 'tool_call_id': 'toolu_01YAbLBoKozJyRQnB8LUMpXC', 'artifact': None, 'status': 'success'}]} diff --git a/docs/docs/cloud/how-tos/human_in_the_loop_time_travel.md b/docs/docs/cloud/how-tos/human_in_the_loop_time_travel.md index 046bb95a8..69b418784 100644 --- a/docs/docs/cloud/how-tos/human_in_the_loop_time_travel.md +++ b/docs/docs/cloud/how-tos/human_in_the_loop_time_travel.md @@ -15,6 +15,7 @@ First, we need to setup our client so that we can communicate with our hosted gr ```python from langgraph_sdk import get_client client = get_client(url=) + # Using the graph deployed with the name "agent" assistant_id = "agent" thread = await client.threads.create() ``` @@ -25,7 +26,8 @@ First, we need to setup our client so that we can communicate with our hosted gr import { Client } from "@langchain/langgraph-sdk"; const client = new Client({ apiUrl: }); - const assistantId = agent; + // Using the graph deployed with the name "agent" + const assistantId = "agent"; const thread = await client.threads.create(); ``` @@ -34,8 +36,7 @@ First, we need to setup our client so that we can communicate with our hosted gr ```bash curl --request POST \ --url /threads \ - --header 'Content-Type: application/json' \ - --data {} + --header 'Content-Type: application/json' ``` ## Replay a state @@ -51,7 +52,7 @@ Before replaying a state - we need to create states to replay from! In order to async for chunk in client.runs.stream( thread["thread_id"], - assistant_id, # graph_id + assistant_id, input=input, stream_mode="updates", ): @@ -308,7 +309,7 @@ Now we can rerun our graph with this new config, starting from the `new_state`, ```python async for chunk in client.runs.stream( thread["thread_id"], - assistant["assistant_id"], # graph_id + assistant_id, input=None, stream_mode="updates", checkpoint_id=config['checkpoint_id'] @@ -322,7 +323,7 @@ Now we can rerun our graph with this new config, starting from the `new_state`, ```js const streamResponse = client.runs.stream( thread["thread_id"], - assistant["assistant_id"], + assistantId, { input: null, streamMode: "updates", diff --git a/docs/docs/cloud/how-tos/human_in_the_loop_user_input.md b/docs/docs/cloud/how-tos/human_in_the_loop_user_input.md index 9b9d1392e..435b49158 100644 --- a/docs/docs/cloud/how-tos/human_in_the_loop_user_input.md +++ b/docs/docs/cloud/how-tos/human_in_the_loop_user_input.md @@ -25,6 +25,7 @@ First, we need to setup our client so that we can communicate with our hosted gr ```python from langgraph_sdk import get_client client = get_client(url=) + # Using the graph deployed with the name "agent" assistant_id = "agent" thread = await client.threads.create() ``` @@ -35,6 +36,7 @@ First, we need to setup our client so that we can communicate with our hosted gr import { Client } from "@langchain/langgraph-sdk"; const client = new Client({ apiUrl: }); + // Using the graph deployed with the name "agent" const assistantId = "agent"; const thread = await client.threads.create(); ``` @@ -56,7 +58,14 @@ Now, let's invoke our graph by interrupting before `ask_human` node: === "Python" ```python - input = { 'messages':[{ "role":"user", "content":"Use the search tool to ask the user where they are, then look up the weather there" }] } + input = { + "messages": [ + { + "role": "human", + "content": "Use the search tool to ask the user where they are, then look up the weather there", + } + ] + } async for chunk in client.runs.stream( thread["thread_id"], @@ -71,7 +80,14 @@ Now, let's invoke our graph by interrupting before `ask_human` node: === "Javascript" ```js - const input = { "messages":[{ "role":"human", "content": "Use the search tool to ask the user where they are, then look up the weather there"}] } + const input = { + messages: [ + { + role: "human", + content: "Use the search tool to ask the user where they are, then look up the weather there" + } + ] + }; const streamResponse = client.runs.stream( thread["thread_id"], @@ -79,9 +95,10 @@ Now, let's invoke our graph by interrupting before `ask_human` node: { input: input, streamMode: "updates", - interruptBefore: ["ask_human"], + interruptBefore: ["ask_human"] } ); + for await (const chunk of streamResponse) { if (chunk.data && chunk.event !== "metadata") { console.log(chunk.data); @@ -152,13 +169,23 @@ Because we are treating this as a tool call, we will need to update the state as === "Javascript" ```js - const state = await client.threads.getState(thread['thread_id']); - const toolCallId = state['values']['messages'][-1]['tool_calls'][0]['id']; + const state = await client.threads.getState(thread["thread_id"]); + const toolCallId = state.values.messages[state.values.messages.length - 1].tool_calls[0].id; - # We now create the tool call with the id and the response we want - const toolMessage = [{"tool_call_id": toolCallId, "type": "tool", "content": "san francisco"}]; + // We now create the tool call with the id and the response we want + const toolMessage = [ + { + tool_call_id: toolCallId, + type: "tool", + content: "san francisco" + } + ]; - await client.threads.updateState(thread['thread_id'], {values: {"messages": toolMessage}, asNode:"ask_human"}) + await client.threads.updateState( + thread["thread_id"], + { values: { messages: toolMessage } }, + { asNode: "ask_human" } + ); ``` === "CURL" @@ -212,9 +239,10 @@ We can now tell the agent to continue. We can just pass in None as the input to assistantId, { input: null, - streamMode: "updates", + streamMode: "updates" } ); + for await (const chunk of streamResponse) { if (chunk.data && chunk.event !== "metadata") { console.log(chunk.data); diff --git a/docs/docs/cloud/how-tos/index.md b/docs/docs/cloud/how-tos/index.md index 90b13c35e..25954dd3b 100644 --- a/docs/docs/cloud/how-tos/index.md +++ b/docs/docs/cloud/how-tos/index.md @@ -62,17 +62,17 @@ LangGraph Studio is a built-in UI for visualizing, testing, and debugging your a LangGraph Cloud supports multiple types of runs besides streaming runs. -- [How to run an agent in the background](cloud_examples/background_run.ipynb) -- [How to run multiple agents in the same thread](cloud_examples/same-thread.ipynb) -- [How to create cron jobs](cloud_examples/cron_jobs.ipynb) -- [How to create stateless runs](cloud_examples/stateless_runs.ipynb) +- [How to run an agent in the background](./background_run.md) +- [How to run multiple agents in the same thread](./same-thread.md) +- [How to create cron jobs](./cron_jobs.md) +- [How to create stateless runs](./stateless_runs.md) ## Other Other guides that may prove helpful! -- [How to configure agents](cloud_examples/configuration_cloud.ipynb) +- [How to configure agents](./configuration_cloud.md) - [How to convert LangGraph calls to LangGraph cloud calls](cloud_examples/langgraph_to_langgraph_cloud.ipynb) -- [How to integrate webhooks](cloud_examples/webhooks.ipynb) +- [How to integrate webhooks](./webhooks.md) - [How to copy threads](./copy_threads.md) - [How to check status of your threads](./check_thread_status.md) diff --git a/docs/docs/cloud/how-tos/interrupt_concurrent.md b/docs/docs/cloud/how-tos/interrupt_concurrent.md index b09f908ef..5ac9934b5 100644 --- a/docs/docs/cloud/how-tos/interrupt_concurrent.md +++ b/docs/docs/cloud/how-tos/interrupt_concurrent.md @@ -4,20 +4,44 @@ This guide assumes knowledge of what double-texting is, which you can learn abou The guide covers the `interrupt` option for double texting, which interrupts the prior run of the graph and starts a new one with the double-text. This option does not delete the first run, but rather keeps it in the database but sets its status to `interrupted`. Below is a quick example of using the `interrupt` option. -First, we will define a quick helper function for printing out JS model outputs (you can skip this if using Python): +First, we will define a quick helper function for printing out JS and CURL model outputs (you can skip this if using Python): -```js -function prettyPrint(m) { - const padded = " " + m['type'] + " "; - const sepLen = Math.floor((80 - padded.length) / 2); - const sep = "=".repeat(sepLen); - const secondSep = sep + (padded.length % 2 ? "=" : ""); - - console.log(`${sep}${padded}${secondSep}`); - console.log("\n\n"); - console.log(m.content); -} -``` +=== "Javascript" + + ```js + function prettyPrint(m) { + const padded = " " + m['type'] + " "; + const sepLen = Math.floor((80 - padded.length) / 2); + const sep = "=".repeat(sepLen); + const secondSep = sep + (padded.length % 2 ? "=" : ""); + + console.log(`${sep}${padded}${secondSep}`); + console.log("\n\n"); + console.log(m.content); + } + ``` + +=== "CURL" + + ```bash + # PLACE THIS IN A FILE CALLED pretty_print.sh + pretty_print() { + local type="$1" + local content="$2" + local padded=" $type " + local total_width=80 + local sep_len=$(( (total_width - ${#padded}) / 2 )) + local sep=$(printf '=%.0s' $(eval "echo {1.."${sep_len}"}")) + local second_sep=$sep + if (( (total_width - ${#padded}) % 2 )); then + second_sep="${second_sep}=" + fi + + echo "${sep}${padded}${second_sep}" + echo + echo "$content" + } + ``` Now, let's import our required packages and instantiate our client, assistant, and thread. @@ -30,6 +54,7 @@ Now, let's import our required packages and instantiate our client, assistant, a from langgraph_sdk import get_client client = get_client(url=) + # Using the graph deployed with the name "agent" assistant_id = "agent" thread = await client.threads.create() ``` @@ -40,11 +65,20 @@ Now, let's import our required packages and instantiate our client, assistant, a import { Client } from "@langchain/langgraph-sdk"; const client = new Client({ apiUrl: }); + // Using the graph deployed with the name "agent" const assistantId = "agent"; const thread = await client.threads.create(); ``` -Now we can start our two runs and join the second one until it has completed: +=== "CURL" + + ```bash + curl --request POST \ + --url /threads \ + --header 'Content-Type: application/json' + ``` + +Now we can start our two runs and join the second on euntil it has completed: === "Python" @@ -90,6 +124,26 @@ Now we can start our two runs and join the second one until it has completed: await client.runs.join(thread["thread_id"], run["run_id"]); ``` +=== "CURL" + + ```bash + curl --request POST \ + --url >/threads//runs \ + --header 'Content-Type: application/json' \ + --data "{ + \"assistant_id\": \"agent\", + \"input\": {\"messages\": [{\"role\": \"human\", \"content\": \"what\'s the weather in sf?\"}]}, + }" && sleep 2 && curl --request POST \ + --url >/threads//runs \ + --header 'Content-Type: application/json' \ + --data "{ + \"assistant_id\": \"agent\", + \"input\": {\"messages\": [{\"role\": \"human\", \"content\": \"what\'s the weather in nyc?\"}]}, + \"multitask_strategy\": \"interrupt\" + }" && curl --request GET \ + --url /threads//runs//join + ``` + We can see that the thread has partial data from the first run + data from the second run @@ -112,12 +166,24 @@ We can see that the thread has partial data from the first run + data from the s } ``` +=== "CURL" + + ```bash + source pretty_print.sh && curl --request GET \ + --url /threads//state | \ + jq -c '.values.messages[]' | while read -r element; do + type=$(echo "$element" | jq -r '.type') + content=$(echo "$element" | jq -r '.content | if type == "array" then tostring else . end') + pretty_print "$type" "$content" + done + ``` + Output: - ================================ Human Message ================================= + ================================ Human Message ================================= what's the weather in sf? - ================================== Ai Message ================================== + ================================== Ai Message ================================== [{'id': 'toolu_01MjNtVJwEcpujRGrf3x6Pih', 'input': {'query': 'weather in san francisco'}, 'name': 'tavily_search_results_json', 'type': 'tool_use'}] Tool Calls: @@ -125,14 +191,14 @@ Output: Call ID: toolu_01MjNtVJwEcpujRGrf3x6Pih Args: query: weather in san francisco - ================================= Tool Message ================================= + ================================= Tool Message ================================= Name: tavily_search_results_json [{"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 ..."}] - ================================ Human Message ================================= + ================================ Human Message ================================= what's the weather in nyc? - ================================== Ai Message ================================== + ================================== Ai Message ================================== [{'id': 'toolu_01KtE1m1ifPLQAx4fQLyZL9Q', 'input': {'query': 'weather in new york city'}, 'name': 'tavily_search_results_json', 'type': 'tool_use'}] Tool Calls: @@ -140,11 +206,11 @@ Output: Call ID: toolu_01KtE1m1ifPLQAx4fQLyZL9Q Args: query: weather in new york city - ================================= Tool Message ================================= + ================================= Tool Message ================================= Name: tavily_search_results_json [{"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."}] - ================================== Ai Message ================================== + ================================== Ai Message ================================== 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: diff --git a/docs/docs/cloud/how-tos/reject_concurrent.md b/docs/docs/cloud/how-tos/reject_concurrent.md index 22d0ae30c..d94bb27a6 100644 --- a/docs/docs/cloud/how-tos/reject_concurrent.md +++ b/docs/docs/cloud/how-tos/reject_concurrent.md @@ -4,20 +4,44 @@ This guide assumes knowledge of what double-texting is, which you can learn abou The guide covers the `reject` option for double texting, which rejects the new run of the graph by throwing an error and continues with the original run until completion. Below is a quick example of using the `reject` option. -First, we will define a quick helper function for printing out JS model outputs (you can skip this if using Python): +First, we will define a quick helper function for printing out JS and CURL model outputs (you can skip this if using Python): -```js -function prettyPrint(m) { - const padded = " " + m['type'] + " "; - const sepLen = Math.floor((80 - padded.length) / 2); - const sep = "=".repeat(sepLen); - const secondSep = sep + (padded.length % 2 ? "=" : ""); - - console.log(`${sep}${padded}${secondSep}`); - console.log("\n\n"); - console.log(m.content); -} -``` +=== "Javascript" + + ```js + function prettyPrint(m) { + const padded = " " + m['type'] + " "; + const sepLen = Math.floor((80 - padded.length) / 2); + const sep = "=".repeat(sepLen); + const secondSep = sep + (padded.length % 2 ? "=" : ""); + + console.log(`${sep}${padded}${secondSep}`); + console.log("\n\n"); + console.log(m.content); + } + ``` + +=== "CURL" + + ```bash + # PLACE THIS IN A FILE CALLED pretty_print.sh + pretty_print() { + local type="$1" + local content="$2" + local padded=" $type " + local total_width=80 + local sep_len=$(( (total_width - ${#padded}) / 2 )) + local sep=$(printf '=%.0s' $(eval "echo {1.."${sep_len}"}")) + local second_sep=$sep + if (( (total_width - ${#padded}) % 2 )); then + second_sep="${second_sep}=" + fi + + echo "${sep}${padded}${second_sep}" + echo + echo "$content" + } + ``` Now, let's import our required packages and instantiate our client, assistant, and thread. @@ -29,6 +53,7 @@ Now, let's import our required packages and instantiate our client, assistant, a from langgraph_sdk import get_client client = get_client(url=) + # Using the graph deployed with the name "agent" assistant_id = "agent" thread = await client.threads.create() ``` @@ -39,10 +64,19 @@ Now, let's import our required packages and instantiate our client, assistant, a import { Client } from "@langchain/langgraph-sdk"; const client = new Client({ apiUrl: }); + // Using the graph deployed with the name "agent" const assistantId = "agent"; const thread = await client.threads.create(); ``` +=== "CURL" + + ```bash + curl --request POST \ + --url /threads \ + --header 'Content-Type: application/json' + ``` + Now we can run a thread and try to run a second one with the "reject" option, which should fail since we have already started a run: @@ -90,6 +124,27 @@ Now we can run a thread and try to run a second one with the "reject" option, wh } ``` +=== "CURL" + + ```bash + curl --request POST \ + --url >/threads//runs \ + --header 'Content-Type: application/json' \ + --data "{ + \"assistant_id\": \"agent\", + \"input\": {\"messages\": [{\"role\": \"human\", \"content\": \"what\'s the weather in sf?\"}]}, + }" && curl --request POST \ + --url >/threads//runs \ + --header 'Content-Type: application/json' \ + --data "{ + \"assistant_id\": \"agent\", + \"input\": {\"messages\": [{\"role\": \"human\", \"content\": \"what\'s the weather in nyc?\"}]}, + \"multitask_strategy\": \"reject\" + }" || { echo "Failed to start concurrent run"; echo "Error: $?" >&2; } + ``` + +Output: + Failed to start concurrent run Client error '409 Conflict' for url 'http://localhost:8123/threads/f9e7088b-8028-4e5c-88d2-9cc9a2870e50/runs' For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/409 @@ -120,12 +175,25 @@ We can verify that the original thread finished executing: } ``` +=== "CURL" + + ```bash + source pretty_print.sh && curl --request GET \ + --url /threads//runs//join && \ + curl --request GET --url /threads//state | \ + jq -c '.values.messages[]' | while read -r element; do + type=$(echo "$element" | jq -r '.type') + content=$(echo "$element" | jq -r '.content | if type == "array" then tostring else . end') + pretty_print "$type" "$content" + done + ``` + Output: - ================================ Human Message ================================= + ================================ Human Message ================================= what's the weather in sf? - ================================== Ai Message ================================== + ================================== Ai Message ================================== [{'id': 'toolu_01CyewEifV2Kmi7EFKHbMDr1', 'input': {'query': 'weather in san francisco'}, 'name': 'tavily_search_results_json', 'type': 'tool_use'}] Tool Calls: @@ -133,11 +201,11 @@ Output: Call ID: toolu_01CyewEifV2Kmi7EFKHbMDr1 Args: query: weather in san francisco - ================================= Tool Message ================================= + ================================= Tool Message ================================= Name: tavily_search_results_json [{"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."}] - ================================== Ai Message ================================== + ================================== Ai Message ================================== According to the search results from Tavily, the current weather in San Francisco is: diff --git a/docs/docs/cloud/how-tos/rollback_concurrent.md b/docs/docs/cloud/how-tos/rollback_concurrent.md index 6fa34eb95..cabc584c0 100644 --- a/docs/docs/cloud/how-tos/rollback_concurrent.md +++ b/docs/docs/cloud/how-tos/rollback_concurrent.md @@ -4,20 +4,44 @@ This guide assumes knowledge of what double-texting is, which you can learn abou The guide covers the `rollback` option for double texting, which interrupts the prior run of the graph and starts a new one with the double-text. This option is very similar to the `interrupt` option, but in this case the first run is completely deleted from the database and cannot be restarted. Below is a quick example of using the `rollback` option. -First, we will define a quick helper function for printing out JS model outputs (you can skip this if using Python): +First, we will define a quick helper function for printing out JS and CURL model outputs (you can skip this if using Python): -```js -function prettyPrint(m) { - const padded = " " + m['type'] + " "; - const sepLen = Math.floor((80 - padded.length) / 2); - const sep = "=".repeat(sepLen); - const secondSep = sep + (padded.length % 2 ? "=" : ""); - - console.log(`${sep}${padded}${secondSep}`); - console.log("\n\n"); - console.log(m.content); -} -``` +=== "Javascript" + + ```js + function prettyPrint(m) { + const padded = " " + m['type'] + " "; + const sepLen = Math.floor((80 - padded.length) / 2); + const sep = "=".repeat(sepLen); + const secondSep = sep + (padded.length % 2 ? "=" : ""); + + console.log(`${sep}${padded}${secondSep}`); + console.log("\n\n"); + console.log(m.content); + } + ``` + +=== "CURL" + + ```bash + # PLACE THIS IN A FILE CALLED pretty_print.sh + pretty_print() { + local type="$1" + local content="$2" + local padded=" $type " + local total_width=80 + local sep_len=$(( (total_width - ${#padded}) / 2 )) + local sep=$(printf '=%.0s' $(eval "echo {1.."${sep_len}"}")) + local second_sep=$sep + if (( (total_width - ${#padded}) % 2 )); then + second_sep="${second_sep}=" + fi + + echo "${sep}${padded}${second_sep}" + echo + echo "$content" + } + ``` Now, let's import our required packages and instantiate our client, assistant, and thread. @@ -31,6 +55,7 @@ Now, let's import our required packages and instantiate our client, assistant, a from langgraph_sdk import get_client client = get_client(url=) + # Using the graph deployed with the name "agent" assistant_id = "agent" thread = await client.threads.create() ``` @@ -41,10 +66,19 @@ Now, let's import our required packages and instantiate our client, assistant, a import { Client } from "@langchain/langgraph-sdk"; const client = new Client({ apiUrl: }); + // Using the graph deployed with the name "agent" const assistantId = "agent"; const thread = await client.threads.create(); ``` +=== "CURL" + + ```bash + curl --request POST \ + --url /threads \ + --header 'Content-Type: application/json' + ``` + Now let's run a thread with the multitask parameter set to "rollback": === "Python" @@ -91,6 +125,26 @@ Now let's run a thread with the multitask parameter set to "rollback": await client.runs.join(thread["thread_id"], run["run_id"]); ``` +=== "CURL" + + ```bash + curl --request POST \ + --url >/threads//runs \ + --header 'Content-Type: application/json' \ + --data "{ + \"assistant_id\": \"agent\", + \"input\": {\"messages\": [{\"role\": \"human\", \"content\": \"what\'s the weather in sf?\"}]}, + }" && sleep 2 && curl --request POST \ + --url >/threads//runs \ + --header 'Content-Type: application/json' \ + --data "{ + \"assistant_id\": \"agent\", + \"input\": {\"messages\": [{\"role\": \"human\", \"content\": \"what\'s the weather in nyc?\"}]}, + \"multitask_strategy\": \"rollback\" + }" && curl --request GET \ + --url /threads//runs//join + ``` + We can see that the thread has data only from the second run === "Python" @@ -112,12 +166,24 @@ We can see that the thread has data only from the second run } ``` +=== "CURL" + + ```bash + source pretty_print.sh && curl --request GET \ + --url /threads//state | \ + jq -c '.values.messages[]' | while read -r element; do + type=$(echo "$element" | jq -r '.type') + content=$(echo "$element" | jq -r '.content | if type == "array" then tostring else . end') + pretty_print "$type" "$content" + done + ``` + Output: - ================================ Human Message ================================= + ================================ Human Message ================================= what's the weather in nyc? - ================================== Ai Message ================================== + ================================== Ai Message ================================== [{'id': 'toolu_01JzPqefao1gxwajHQ3Yh3JD', 'input': {'query': 'weather in nyc'}, 'name': 'tavily_search_results_json', 'type': 'tool_use'}] Tool Calls: @@ -125,11 +191,11 @@ Output: Call ID: toolu_01JzPqefao1gxwajHQ3Yh3JD Args: query: weather in nyc - ================================= Tool Message ================================= + ================================= Tool Message ================================= Name: tavily_search_results_json [{"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}}"}] - ================================== Ai Message ================================== + ================================== Ai Message ================================== 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. diff --git a/docs/docs/cloud/how-tos/same-thread.md b/docs/docs/cloud/how-tos/same-thread.md new file mode 100644 index 000000000..f48e72b6f --- /dev/null +++ b/docs/docs/cloud/how-tos/same-thread.md @@ -0,0 +1,310 @@ +# How to run multiple agents on the same thread + +In LangGraph Cloud, a thread is not explicitly associated with a particular agent. +This means that you can run multiple agents on the same thread, which allows a different agent to continue from an initial agent's progress. + +In this example, we will create two agents and then call them both on the same thread. +You'll see that the second agent will respond using information from the [checkpoint](https://langchain-ai.github.io/langgraph/concepts/low_level/#checkpointer-state) generated in the thread by the first agent as context. + +=== "Python" + + ```python + from langgraph_sdk import get_client + + client = get_client(url=) + + openai_assistant = await client.assistants.create( + graph_id="agent", config={"configurable": {"model_name": "openai"}} + ) + + # There should always be a default assistant with no configuration + assistants = await client.assistants.search() + default_assistant = [a for a in assistants if not a["config"]][0] + ``` + +=== "Javascript" + + ```js + import { Client } from "@langchain/langgraph-sdk"; + + const client = new Client({ apiUrl: }); + + const openAIAssistant = await client.assistants.create( + { graphId: "agent", config: {"configurable": {"model_name": "openai"}}} + ); + + const assistants = await client.assistants.search(); + const defaultAssistant = assistants.find(a => !a.config); + ``` + +=== "CURL" + + ```bash + curl --request POST \ + --url /assistants \ + --header 'Content-Type: application/json' \ + --data '{ + "graph_id": "agent", + "config": { "configurable": { "model_name": "openai" } } + }' && \ + curl --request POST \ + --url /assistants/search \ + --header 'Content-Type: application/json' \ + --data '{ + "limit": 10, + "offset": 0 + }' | jq -c 'map(select(.config == null or .config == {})) | .[0]' + ``` + +We can see that these agents are different: + +=== "Python" + + ```python + print(openai_assistant) + ``` + +=== "Javascript" + + ```js + console.log(openAIAssistant); + ``` + +=== "CURL" + + ```bash + curl --request GET \ + --url /assistants/ + ``` + +Output: + + { + "assistant_id": "db87f39d-b2b1-4da8-ac65-cf81beb3c766", + "graph_id": "agent", + "created_at": "2024-08-30T21:18:51.850581+00:00", + "updated_at": "2024-08-30T21:18:51.850581+00:00", + "config": { + "configurable": { + "model_name": "openai" + } + }, + "metadata": {} + } + +=== "Python" + + ```python + print(default_assistant) + ``` + +=== "Javascript" + + ```js + console.log(defaultAssistant); + ``` + +=== "CURL" + + ```bash + curl --request GET \ + --url /assistants/ + ``` + +Output: + + { + "assistant_id": "fe096781-5601-53d2-b2f6-0d3403f7e9ca", + "graph_id": "agent", + "created_at": "2024-08-08T22:45:24.562906+00:00", + "updated_at": "2024-08-08T22:45:24.562906+00:00", + "config": {}, + "metadata": { + "created_by": "system" + } + } + +We can now run the OpenAI assistant on the thread first. + +=== "Python" + + ```python + thread = await client.threads.create() + input = {"messages": [{"role": "user", "content": "who made you?"}]} + async for event in client.runs.stream( + thread["thread_id"], + openai_assistant["assistant_id"], + input=input, + stream_mode="updates", + ): + print(f"Receiving event of type: {event.event}") + print(event.data) + print("\n\n") + ``` + +=== "Javascript" + + ```js + const thread = await client.threads.create(); + let input = {"messages": [{"role": "user", "content": "who made you?"}]} + + const streamResponse = client.runs.stream( + thread["thread_id"], + openAIAssistant["assistant_id"], + { + input, + streamMode: "updates" + } + ); + for await (const event of streamResponse) { + console.log(`Receiving event of type: ${event.event}`); + console.log(event.data); + console.log("\n\n"); + } + ``` + +=== "CURL" + + ```bash + thread_id=$(curl --request POST \ + --url /threads \ + --header 'Content-Type: application/json' \ + --data '{}' | jq -r '.thread_id') && \ + curl --request POST \ + --url "/threads/${thread_id}/runs/stream" \ + --header 'Content-Type: application/json' \ + --data '{ + "assistant_id": , + "input": { + "messages": [ + { + "role": "human", + "content": "who made you?" + } + ] + }, + "stream_mode": [ + "updates" + ] + }' | \ + sed 's/\r$//' | \ + awk ' + /^event:/ { + if (data_content != "") { + print data_content "\n" + } + sub(/^event: /, "Receiving event of type: ", $0) + printf "%s...\n", $0 + data_content = "" + } + /^data:/ { + sub(/^data: /, "", $0) + data_content = $0 + } + END { + if (data_content != "") { + print data_content "\n\n" + } + } + ' + ``` + +Output: + + Receiving event of type: metadata + {'run_id': '1ef671c5-fb83-6e70-b698-44dba2d9213e'} + + + Receiving event of type: updates + {'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', 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_157b3831f5'}, 'type': 'ai', 'name': None, 'id': 'run-f5735b86-b80d-4c71-8dc3-4782b5a9c7c8', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]}} + +Now, we can run it on the default assistant and see that this second assistant is aware of the initial question, and can answer the question, "and you?": + +=== "Python" + + ```python + input = {"messages": [{"role": "user", "content": "and you?"}]} + async for event in client.runs.stream( + thread["thread_id"], + default_assistant["assistant_id"], + input=input, + stream_mode="updates", + ): + print(f"Receiving event of type: {event.event}") + print(event.data) + print("\n\n") + ``` + +=== "Javascript" + + ```js + let input = {"messages": [{"role": "user", "content": "and you?"}]} + + const streamResponse = client.runs.stream( + thread["thread_id"], + defaultAssistant["assistant_id"], + { + input, + streamMode: "updates" + } + ); + for await (const event of streamResponse) { + console.log(`Receiving event of type: ${event.event}`); + console.log(event.data); + console.log("\n\n"); + } + ``` + +=== "CURL" + + ```bash + curl --request POST \ + --url /threads//runs/stream \ + --header 'Content-Type: application/json' \ + --data '{ + "assistant_id": , + "input": { + "messages": [ + { + "role": "human", + "content": "and you?" + } + ] + }, + "stream_mode": [ + "updates" + ] + }' | \ + sed 's/\r$//' | \ + awk ' + /^event:/ { + if (data_content != "") { + print data_content "\n" + } + sub(/^event: /, "Receiving event of type: ", $0) + printf "%s...\n", $0 + data_content = "" + } + /^data:/ { + sub(/^data: /, "", $0) + data_content = $0 + } + END { + if (data_content != "") { + print data_content "\n\n" + } + } + ' + ``` + +Output: + + Receiving event of type: metadata + {'run_id': '1ef6722d-80b3-6fbb-9324-253796b1cd13'} + + + Receiving event of type: updates + {'agent': {'messages': [{'content': [{'text': 'I am an artificial intelligence created by Anthropic, not by OpenAI. I should not have stated that OpenAI created me, as that is incorrect. Anthropic is the company that developed and trained me using advanced language models and AI technology. I will be more careful about providing accurate information regarding my origins in the future.', 'type': 'text', 'index': 0}], 'additional_kwargs': {}, 'response_metadata': {'stop_reason': 'end_turn', 'stop_sequence': None}, 'type': 'ai', 'name': None, 'id': 'run-ebaacf62-9dd9-4165-9535-db432e4793ec', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': {'input_tokens': 302, 'output_tokens': 72, 'total_tokens': 374}}]}} + + + diff --git a/docs/docs/cloud/how-tos/stateless_runs.md b/docs/docs/cloud/how-tos/stateless_runs.md new file mode 100644 index 000000000..e28363963 --- /dev/null +++ b/docs/docs/cloud/how-tos/stateless_runs.md @@ -0,0 +1,180 @@ +# Stateless Runs + +Most of the time, you provide a `thread_id` to your client when you run your graph in order to keep track of prior runs through the persistent state implemented in LangGraph Cloud. However, if you don't need to persist the runs you don't need to use the built in persistent state and can create stateless runs. + +## Setup + +First, let's setup our client: + +=== "Python" + + ```python + from langgraph_sdk import get_client + + client = get_client(url=) + # Using the graph deployed with the name "agent" + assistant_id = "agent" + # create thread + thread = await client.threads.create() + ``` + +=== "Javascript" + + ```js + import { Client } from "@langchain/langgraph-sdk"; + + const client = new Client({ apiUrl: }); + // Using the graph deployed with the name "agent" + const assistantId = "agent"; + // create thread + const thread = await client.threads.create(); + ``` + +=== "CURL" + + ```bash + curl --request POST \ + --url /assistants/search \ + --header 'Content-Type: application/json' \ + --data '{ + "limit": 10, + "offset": 0 + }' | jq -c 'map(select(.config == null or .config == {})) | .[0].graph_id' && \ + curl --request POST \ + --url /threads \ + --header 'Content-Type: application/json' \ + --data '{}' + ``` + +## Stateless streaming + +We can stream the results of a stateless run in an almost identical fashion to how we stream from a run with the state attribute, but instead of passing a value to the `thread_id` parameter, we pass `None`: + +=== "Python" + + ```python + input = { + "messages": [ + {"role": "user", "content": "Hello! My name is Bagatur and I am 26 years old."} + ] + } + + async for chunk in client.runs.stream( + # Don't pass in a thread_id and the stream will be stateless + None, + assistant_id, + input=input, + stream_mode="updates", + ): + if chunk.data and "run_id" not in chunk.data: + print(chunk.data) + ``` + +=== "Javascript" + + ```js + let input = { + messages: [ + { role: "user", content: "Hello! My name is Bagatur and I am 26 years old." } + ] + }; + + const streamResponse = client.runs.stream( + // Don't pass in a thread_id and the stream will be stateless + null, + assistantId, + { + input, + streamMode: "updates" + } + ); + for await (const chunk of streamResponse) { + if (chunk.data && !("run_id" in chunk.data)) { + console.log(chunk.data); + } + } + ``` + +=== "CURL" + + ```bash + curl --request POST \ + --url /threads//runs/stream \ + --header 'Content-Type: application/json' \ + --data "{ + \"assistant_id\": \"agent\", + \"input\": {\"messages\": [{\"role\": \"human\", \"content\": \"Hello! My name is Bagatur and I am 26 years old.\"}]}, + \"stream_mode\": [ + \"updates\" + ] + }" | jq -c 'select(.data and (.data | has("run_id") | not)) | .data' + ``` + +Output: + + {'agent': {'messages': [{'content': "Hello Bagatur! It's nice to meet you. Thank you for introducing yourself and sharing your age. Is there anything specific you'd like to know or discuss? I'm here to help with any questions or topics you're interested in.", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-489ec573-1645-4ce2-a3b8-91b391d50a71', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]}} + +## Waiting for stateless results + +In addition to streaming, you can also wait for a stateless result by using the `.wait` function like follows: + +=== "Python" + + ```python + stateless_run_result = await client.runs.wait( + None, + assistant_id, + input=input, + ) + print(stateless_run_result) + ``` + +=== "Javascript" + + ```js + let statelessRunResult = await client.runs.wait( + null, + assistantId, + { input: input } + ); + console.log(statelessRunResult); + ``` + +=== "CURL" + + ```bash + curl --request POST \ + --url /runs/runs/wait \ + --header 'Content-Type: application/json' \ + --data '{ + "assistant_id": , + }' + ``` + +Output: + + { + 'messages': [ + { + 'content': 'Hello! My name is Bagatur and I am 26 years old.', + 'additional_kwargs': {}, + 'response_metadata': {}, + 'type': 'human', + 'name': None, + 'id': '5e088543-62c2-43de-9d95-6086ad7f8b48', + 'example': False} + , + { + 'content': "Hello Bagatur! It's nice to meet you. Thank you for introducing yourself and sharing your age. Is there anything specific you'd like to know or discuss? I'm here to help with any questions or topics you'd like to explore.", + 'additional_kwargs': {}, + 'response_metadata': {}, + 'type': 'ai', + 'name': None, + 'id': 'run-d6361e8d-4d4c-45bd-ba47-39520257f773', + 'example': False, + 'tool_calls': [], + 'invalid_tool_calls': [], + 'usage_metadata': None + } + ] + } \ No newline at end of file diff --git a/docs/docs/cloud/how-tos/stream_debug.md b/docs/docs/cloud/how-tos/stream_debug.md index fe3245947..2dac3d7d7 100644 --- a/docs/docs/cloud/how-tos/stream_debug.md +++ b/docs/docs/cloud/how-tos/stream_debug.md @@ -14,6 +14,8 @@ First let's set up our client and thread: from langgraph_sdk import get_client client = get_client(url=) + # Using the graph deployed with the name "agent" + assistant_id = "agent" # create thread thread = await client.threads.create() print(thread) @@ -25,18 +27,33 @@ First let's set up our client and thread: import { Client } from "@langchain/langgraph-sdk"; const client = new Client({ apiUrl: }); + // Using the graph deployed with the name "agent" + const assistantID = "agent"; // create thread const thread = await client.threads.create(); - console.log(thread) + console.log(thread); + ``` + +=== "CURL" + + ```bash + curl --request POST \ + --url /threads \ + --header 'Content-Type: application/json' ``` Output: - {'thread_id': 'd0cbe9ad-f11c-443a-9f6f-dca0ae5a0dd3', - 'created_at': '2024-06-21T22:10:27.696862+00:00', - 'updated_at': '2024-06-21T22:10:27.696862+00:00', - 'metadata': {}} + { + 'thread_id': 'd0cbe9ad-f11c-443a-9f6f-dca0ae5a0dd3', + 'created_at': '2024-06-21T22:10:27.696862+00:00', + 'updated_at': '2024-06-21T22:10:27.696862+00:00', + 'metadata': {}, + 'status': 'idle', + 'config': {}, + 'values': None + } @@ -56,7 +73,7 @@ Output: # stream debug async for chunk in client.runs.stream( thread_id=thread["thread_id"], - assistant_id="agent", + assistant_id=assistant_id, input=input, stream_mode="debug", ): @@ -70,30 +87,67 @@ Output: ```js // create input const input = { - "messages": [ - { - "role": "human", - "content": "What's the weather in SF?", - } - ] - } + messages: [ + { + role: "human", + content: "What's the weather in SF?", + } + ] + }; // stream debug const streamResponse = client.runs.stream( thread["thread_id"], - "agent", + assistantID, { input, streamMode: "debug" } ); + for await (const chunk of streamResponse) { - console.log(f"Receiving new event of type: {chunk.event}...") - console.log(chunk.data) - console.log("\n\n") + console.log(`Receiving new event of type: ${chunk.event}...`); + console.log(chunk.data); + console.log("\n\n"); } ``` +=== "CURL" + + ```bash + curl --request POST \ + --url /threads//runs/stream \ + --header 'Content-Type: application/json' \ + --data "{ + \"assistant_id\": \"agent\", + \"input\": {\"messages\": [{\"role\": \"human\", \"content\": \"What's the weather in SF?\"}]}, + \"stream_mode\": [ + \"debug\" + ] + }" | \ + sed 's/\r$//' | \ + awk ' + /^event:/ { + if (data_content != "") { + print data_content "\n" + } + sub(/^event: /, "Receiving event of type: ", $0) + printf "%s...\n", $0 + data_content = "" + } + /^data:/ { + sub(/^data: /, "", $0) + data_content = $0 + } + END { + if (data_content != "") { + print data_content "\n" + } + } + ' + ``` + + Output: Receiving new event of type: metadata... diff --git a/docs/docs/cloud/how-tos/stream_events.md b/docs/docs/cloud/how-tos/stream_events.md index bbaaaf7be..fcac1dedc 100644 --- a/docs/docs/cloud/how-tos/stream_events.md +++ b/docs/docs/cloud/how-tos/stream_events.md @@ -8,6 +8,8 @@ This guide covers how to stream events from your graph (`stream_mode="events"`). from langgraph_sdk import get_client client = get_client(url=) + # Using the graph deployed with the name "agent" + assistant_id = "agent" # create thread thread = await client.threads.create() print(thread) @@ -19,9 +21,11 @@ This guide covers how to stream events from your graph (`stream_mode="events"`). import { Client } from "@langchain/langgraph-sdk"; const client = new Client({ apiUrl: }); + // Using the graph deployed with the name "agent" + const assistantID = "agent"; // create thread const thread = await client.threads.create(); - console.log(thread) + console.log(thread); ``` === "CURL" @@ -35,12 +39,15 @@ This guide covers how to stream events from your graph (`stream_mode="events"`). Output: - {'thread_id': '3f4c64e0-f792-4a5e-aa07-a4404e06e0bd', - 'created_at': '2024-06-24T22:16:29.301522+00:00', - 'updated_at': '2024-06-24T22:16:29.301522+00:00', - 'metadata': {}, - 'status': 'idle', - 'config': {}} + { + 'thread_id': '3f4c64e0-f792-4a5e-aa07-a4404e06e0bd', + 'created_at': '2024-06-24T22:16:29.301522+00:00', + 'updated_at': '2024-06-24T22:16:29.301522+00:00', + 'metadata': {}, + 'status': 'idle', + 'config': {}, + 'values': None + } @@ -63,7 +70,7 @@ Streaming events produces responses containing an `event` key (in addition to ot # stream events async for chunk in client.runs.stream( thread_id=thread["thread_id"], - assistant_id="agent", + assistant_id=assistant_id, input=input, stream_mode="events", ): @@ -77,27 +84,27 @@ Streaming events produces responses containing an `event` key (in addition to ot ```js // create input const input = { - "messages": [ - { - "role": "human", - "content": "What's the weather in SF?", - } - ] + "messages": [ + { + "role": "human", + "content": "What's the weather in SF?", + } + ] } // stream events const streamResponse = client.runs.stream( thread["thread_id"], - "agent", + assistantID, { input, streamMode: "events" } ); for await (const chunk of streamResponse) { - console.log(f"Receiving new event of type: {chunk.event}...") - console.log(chunk.data) - console.log("\n\n") + console.log(`Receiving new event of type: ${chunk.event}...`); + console.log(chunk.data); + console.log("\n\n"); } ``` @@ -280,9 +287,6 @@ Output: Receiving new event of type: end... None - - - ## Token-by-Token Streaming @@ -297,7 +301,7 @@ Token-by-token streaming can be implemented with the `events` streaming mode. Th # stream token-by-token async for chunk in client.runs.stream( thread_id=thread["thread_id"], - assistant_id="agent", + assistant_id=assistant_id, input=input, stream_mode="events", ): @@ -318,7 +322,7 @@ Token-by-token streaming can be implemented with the `events` streaming mode. Th // stream events const streamResponse = client.runs.stream( thread["thread_id"], - "agent", + assistantID, { input, streamMode: "events" diff --git a/docs/docs/cloud/how-tos/stream_messages.md b/docs/docs/cloud/how-tos/stream_messages.md index c305a7d85..d52e69ebd 100644 --- a/docs/docs/cloud/how-tos/stream_messages.md +++ b/docs/docs/cloud/how-tos/stream_messages.md @@ -22,10 +22,10 @@ E.g., the state should look something like: import { Annotation, messagesStateReducer } from "@langchain/langgraph"; export const StateAnnotation = Annotation.Root({ - messages: Annotation({ + messages: Annotation({ reducer: messagesStateReducer, default: () => [], - }), + }), }); ``` @@ -46,6 +46,8 @@ First let's set up our client and thread: from langgraph_sdk import get_client client = get_client(url=) + # Using the graph deployed with the name "agent" + assistant_id = "agent" # create thread thread = await client.threads.create() print(thread) @@ -57,9 +59,11 @@ First let's set up our client and thread: import { Client } from "@langchain/langgraph-sdk"; const client = new Client({ apiUrl: }); + // Using the graph deployed with the name "agent" + const assistantID = "agent"; // create thread const thread = await client.threads.create(); - console.log(thread) + console.log(thread); ``` === "CURL" @@ -72,12 +76,15 @@ First let's set up our client and thread: Output: - {'thread_id': 'e1431c95-e241-4d1d-a252-27eceb1e5c86', - 'created_at': '2024-06-21T15:48:59.808924+00:00', - 'updated_at': '2024-06-21T15:48:59.808924+00:00', - 'metadata': {}, - 'status': 'idle', - 'config': {}} + { + 'thread_id': 'e1431c95-e241-4d1d-a252-27eceb1e5c86', + 'created_at': '2024-06-21T15:48:59.808924+00:00', + 'updated_at': '2024-06-21T15:48:59.808924+00:00', + 'metadata': {}, + 'status': 'idle', + 'config': {}, + 'values': None + } Let's also define a helper function for better formatting of the tool calls in messages (for CURL we will define a helper script called `process_stream.sh`) @@ -182,7 +189,7 @@ Now we can stream by messages, which will return complete messages (at the end o async for event in client.runs.stream( thread["thread_id"], - assistant_id="agent", + assistant_id=assistant_id, input=input, config=config, stream_mode="messages", @@ -221,24 +228,25 @@ Now we can stream by messages, which will return complete messages (at the end o ```js const input = { - "messages": [ + messages: [ { - "role": "human", - "content": "What's the weather in sf", + role: "human", + content: "What's the weather in sf", } ] - } - const config = {"configurable": {"model_name": "openai"}} + }; + const config = { configurable: { model_name: "openai" } }; const streamResponse = client.runs.stream( thread["thread_id"], - "agent", + assistantID, { input, config, streamMode: "messages" } ); + for await (const event of streamResponse) { if (event.event === "metadata") { console.log(`Metadata: Run ID - ${event.data.run_id}`); diff --git a/docs/docs/cloud/how-tos/stream_multiple.md b/docs/docs/cloud/how-tos/stream_multiple.md index 2f03fb550..39c87aa7e 100644 --- a/docs/docs/cloud/how-tos/stream_multiple.md +++ b/docs/docs/cloud/how-tos/stream_multiple.md @@ -10,6 +10,8 @@ First let's set up our client and thread: from langgraph_sdk import get_client client = get_client(url=) + # Using the graph deployed with the name "agent" + assistant_id = "agent" # create thread thread = await client.threads.create() print(thread) @@ -21,9 +23,11 @@ First let's set up our client and thread: import { Client } from "@langchain/langgraph-sdk"; const client = new Client({ apiUrl: }); + // Using the graph deployed with the name "agent" + const assistantID = "agent"; // create thread const thread = await client.threads.create(); - console.log(thread) + console.log(thread); ``` === "CURL" @@ -36,12 +40,15 @@ First let's set up our client and thread: Output: - {'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', - 'created_at': '2024-06-24T21:30:07.980789+00:00', - 'updated_at': '2024-06-24T21:30:07.980789+00:00', - 'metadata': {}, - 'status': 'idle', - 'config': {}} + { + 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', + 'created_at': '2024-06-24T21:30:07.980789+00:00', + 'updated_at': '2024-06-24T21:30:07.980789+00:00', + 'metadata': {}, + 'status': 'idle', + 'config': {}, + 'values': None + } When configuring multiple streaming modes for a run, responses for each respective mode will be produced. In the following example, note that a `list` of modes (`messages`, `events`, `debug`) is passed to the `stream_mode` parameter and the response contains `events`, `debug`, `messages/complete`, `messages/metadata`, and `messages/partial` event types. @@ -61,7 +68,7 @@ When configuring multiple streaming modes for a run, responses for each respecti # stream events with multiple streaming modes async for chunk in client.runs.stream( thread_id=thread["thread_id"], - assistant_id="agent", + assistant_id=assistant_id, input=input, stream_mode=["messages", "events", "debug"], ): @@ -75,27 +82,27 @@ When configuring multiple streaming modes for a run, responses for each respecti ```js // create input const input = { - "messages": [ + messages: [ { - "role": "human", - "content": "What's the weather in SF?", + role: "human", + content: "What's the weather in SF?", } ] - } + }; // stream events with multiple streaming modes const streamResponse = client.runs.stream( thread["thread_id"], - "agent", + assistantID, { input, streamMode: ["messages", "events", "debug"] } ); for await (const chunk of streamResponse) { - console.log(f"Receiving new event of type: {chunk.event}...") - console.log(chunk.data) - console.log("\n\n") + console.log(`Receiving new event of type: ${chunk.event}...`); + console.log(chunk.data); + console.log("\n\n"); } ``` @@ -482,5 +489,4 @@ Output: None - diff --git a/docs/docs/cloud/how-tos/stream_updates.md b/docs/docs/cloud/how-tos/stream_updates.md index 52234e5f1..ef101b268 100644 --- a/docs/docs/cloud/how-tos/stream_updates.md +++ b/docs/docs/cloud/how-tos/stream_updates.md @@ -1,6 +1,6 @@ # How to stream state updates of your graph -This guide covers how to use `stream_mode="updates"` for your graph, which will stream the updates to the graph state that are made after each node is executed. This differs from using `stream_mode="values"`: instead of streaming the entire value of the state at each superstep, it only streams the updates from each of the nodes that made an update to the state at that superstep. Read [this conceptual guide](https://langchain-ai.github.io/langgraph/concepts/low_level/#stream-and-astream) to learn more.``` +This guide covers how to use `stream_mode="updates"` for your graph, which will stream the updates to the graph state that are made after each node is executed. This differs from using `stream_mode="values"`: instead of streaming the entire value of the state at each superstep, it only streams the updates from each of the nodes that made an update to the state at that superstep. Read [this conceptual guide](https://langchain-ai.github.io/langgraph/concepts/low_level/#stream-and-astream) to learn more. First let's set up our client and thread: @@ -23,7 +23,7 @@ First let's set up our client and thread: const client = new Client({ apiUrl: }); // create thread const thread = await client.threads.create(); - console.log(thread) + console.log(thread); ``` === "CURL" @@ -36,12 +36,15 @@ First let's set up our client and thread: Output: - {'thread_id': '979e3c89-a702-4882-87c2-7a59a250ce16', - 'created_at': '2024-06-21T15:22:07.453100+00:00', - 'updated_at': '2024-06-21T15:22:07.453100+00:00', - 'metadata': {}, - 'status': 'idle', - 'config': {}} + { + 'thread_id': '979e3c89-a702-4882-87c2-7a59a250ce16', + 'created_at': '2024-06-21T15:22:07.453100+00:00', + 'updated_at': '2024-06-21T15:22:07.453100+00:00', + 'metadata': {}, + 'status': 'idle', + 'config': {}, + 'values': None + } Now we can stream by updates, which outputs updates made to the state by each node after it has executed: @@ -72,13 +75,13 @@ Now we can stream by updates, which outputs updates made to the state by each no ```js const input = { - "messages": [ + messages: [ { - "role": "human", - "content": "What's the weather in la", + role: "human", + content: "What's the weather in la" } ] - } + }; const streamResponse = client.runs.stream( thread["thread_id"], @@ -88,10 +91,11 @@ Now we can stream by updates, which outputs updates made to the state by each no streamMode: "updates" } ); + for await (const chunk of streamResponse) { - console.log(f"Receiving new event of type: {chunk.event}...") - console.log(chunk.data) - console.log("\n\n") + console.log(`Receiving new event of type: ${chunk.event}...`); + console.log(chunk.data); + console.log("\n\n"); } ``` diff --git a/docs/docs/cloud/how-tos/stream_values.md b/docs/docs/cloud/how-tos/stream_values.md index b5c25d11d..d43bc6cea 100644 --- a/docs/docs/cloud/how-tos/stream_values.md +++ b/docs/docs/cloud/how-tos/stream_values.md @@ -1,6 +1,6 @@ # How to stream full state of your graph -This guide covers how to use `stream_mode="values"`, which streams the value of the state at each superstep. This differs from using `stream_mode="updates"`: instead of streaming just the updates to the state from each node, it streams the entire graph state at that superstep. Read [this conceptual guide](https://langchain-ai.github.io/langgraph/concepts/low_level/#stream-and-astream) to learn more.``` +This guide covers how to use `stream_mode="values"`, which streams the value of the state at each superstep. This differs from using `stream_mode="updates"`: instead of streaming just the updates to the state from each node, it streams the entire graph state at that superstep. Read [this conceptual guide](https://langchain-ai.github.io/langgraph/concepts/low_level/#stream-and-astream) to learn more. First let's set up our client and thread: @@ -23,7 +23,7 @@ First let's set up our client and thread: const client = new Client({ apiUrl: }); // create thread const thread = await client.threads.create(); - console.log(thread) + console.log(thread); ``` === "CURL" @@ -36,12 +36,15 @@ First let's set up our client and thread: Output: - {'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', - 'created_at': '2024-06-24T21:30:07.980789+00:00', - 'updated_at': '2024-06-24T21:30:07.980789+00:00', - 'metadata': {}, - 'status': 'idle', - 'config': {}} + { + 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', + 'created_at': '2024-06-24T21:30:07.980789+00:00', + 'updated_at': '2024-06-24T21:30:07.980789+00:00', + 'metadata': {}, + 'status': 'idle', + 'config': {}, + 'values': None + } Now we can stream by values, which streams the full state of the graph after each node has finished executing: @@ -76,9 +79,9 @@ Now we can stream by values, which streams the full state of the graph after eac } ); for await (const chunk of streamResponse) { - console.log(f"Receiving new event of type: {chunk.event}...") - console.log(chunk.data) - console.log("\n\n") + console.log(`Receiving new event of type: ${chunk.event}...`); + console.log(chunk.data); + console.log("\n\n"); } ``` diff --git a/docs/docs/cloud/how-tos/webhooks.md b/docs/docs/cloud/how-tos/webhooks.md new file mode 100644 index 000000000..a7148add4 --- /dev/null +++ b/docs/docs/cloud/how-tos/webhooks.md @@ -0,0 +1,125 @@ +# Use Webhooks + +You may wish to use webhooks in your client, especially when using async streams in case you want to update something in your service once the API call to LangGraph Cloud has finished running. To do so, you will need to expose an endpoint that can accept POST requests, and then pass it to your API request in the "webhook" parameter. + +Currently, the SDK has not exposed this endpoint but you can access it through curl commands as follows. + +The following endpoints accept `webhook` as a parameter: + +- Create Run -> POST /thread/{thread_id}/runs +- Create Thread Cron -> POST /thread/{thread_id}/runs/crons +- Stream Run -> POST /thread/{thread_id}/runs/stream +- Wait Run -> POST /thread/{thread_id}/runs/wait +- Create Cron -> POST /runs/crons +- Stream Run Stateless -> POST /runs/stream +- Wait Run Stateless -> POST /runs/wait + +In this example, we will show calling a webhook after streaming a run. First, let's setup our assistant and thread: + +=== "Python" + + ```python + from langgraph_sdk import get_client + + client = get_client(url=) + # Using the graph deployed with the name "agent" + assistant_id = "agent" + # create thread + thread = await client.threads.create() + print(thread) + ``` + +=== "Javascript" + + ```js + import { Client } from "@langchain/langgraph-sdk"; + + const client = new Client({ apiUrl: }); + // Using the graph deployed with the name "agent" + const assistantID = "agent"; + // create thread + const thread = await client.threads.create(); + console.log(thread); + ``` + +=== "CURL" + + ```bash + curl --request POST \ + --url /assistants/search \ + --header 'Content-Type: application/json' \ + --data '{ + "limit": 10, + "offset": 0 + }' | jq -c 'map(select(.config == null or .config == {})) | .[0]' && \ + curl --request POST \ + --url /threads \ + --header 'Content-Type: application/json' \ + --data '{}' + ``` + +Output: + + { + 'thread_id': '9dde5490-2b67-47c8-aa14-4bfec88af217', + 'created_at': '2024-08-30T23:07:38.242730+00:00', + 'updated_at': '2024-08-30T23:07:38.242730+00:00', + 'metadata': {}, + 'status': 'idle', + 'config': {}, + 'values': None + } + +Now we can invoke a run with a webhook: + +=== "Python" + + ```python + # create input + input = { "messages": [{ "role": "human", "content": "Hello!" }] } + + async for chunk in client.runs.stream( + thread_id=thread["thread_id"], + assistant_id=assistant_id, + input=input, + stream_mode="events", + webhook="your-webhook" + ): + # Do something with the stream output + pass + ``` + +=== "Javascript" + + ```js + // create input + const input = { messages: [{ role: "human", content: "Hello!" }] }; + + // stream events + const streamResponse = client.runs.stream( + thread["thread_id"], + assistantID, + { + input: input, + webhook: "your-webhook" + } + ); + for await (const chunk of streamResponse) { + // Do something with the stream output + } + ``` + +=== "CURL" + + ```bash + curl --request POST \ + --url /threads//runs/stream \ + --header 'Content-Type: application/json' \ + --data '{ + "assistant_id": , + "input" : {"messages":[{"role": "user", "content": "Hello!"}]}, + "webhook": + }' + ``` + +And that's it! Now you can trigger your custom webhooks whenever you want in your LangGraph applications! \ No newline at end of file diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index f9c8138a8..2efeafb7a 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -229,14 +229,14 @@ nav: - Invoke graph in LangGraph Studio: "cloud/how-tos/invoke_studio.md" - Interact with threads in LangGraph Studio: "cloud/how-tos/threads_studio.md" - Different Types of Runs: - - Run an Agent in the Background: "cloud/how-tos/cloud_examples/background_run.ipynb" - - Run Multiple Agents in Same Thread: "cloud/how-tos/cloud_examples/same-thread.ipynb" - - Create Cron Jobs: "cloud/how-tos/cloud_examples/cron_jobs.ipynb" - - Create Stateless Runs: "cloud/how-tos/cloud_examples/stateless_runs.ipynb" + - Run an Agent in the Background: "cloud/how-tos/background_run.md" + - Run Multiple Agents in Same Thread: "cloud/how-tos/same-thread.md" + - Create Cron Jobs: "cloud/how-tos/cron_jobs.md" + - Create Stateless Runs: "cloud/how-tos/stateless_runs.md" - Other: - - Configure Agents: "cloud/how-tos/cloud_examples/configuration_cloud.ipynb" + - Configure Agents: "cloud/how-tos/configuration_cloud.md" - Convert LangGraph calls to LangGraph Cloud calls: "cloud/how-tos/cloud_examples/langgraph_to_langgraph_cloud.ipynb" - - Integrate Webhooks: 'cloud/how-tos/cloud_examples/webhooks.ipynb' + - Integrate Webhooks: 'cloud/how-tos/webhooks.md' - Copy Threads: 'cloud/how-tos/copy_threads.md' - Check Status of Threads: "cloud/how-tos/check_thread_status.md" - Conceptual Guides: diff --git a/examples/cloud_examples/background_run.ipynb b/examples/cloud_examples/background_run.ipynb deleted file mode 100644 index 6279402fc..000000000 --- a/examples/cloud_examples/background_run.ipynb +++ /dev/null @@ -1,388 +0,0 @@ -{ - "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": 1, - "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": 2, - "id": "4947e9bc-111f-4991-8c41-1041da9bf0ba", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'assistant_id': 'e90fee30-be91-43aa-a33c-d54bd219072e',\n", - " 'graph_id': 'agent',\n", - " 'created_at': '2024-06-18T18:06:55.102231+00:00',\n", - " 'updated_at': '2024-06-18T18:06:55.102231+00:00',\n", - " 'config': {'configurable': {'model_name': 'anthropic'}},\n", - " 'metadata': {}}" - ] - }, - "execution_count": 2, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# List available assistants\n", - "assistants = await client.assistants.search()\n", - "assistants[0]" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "230c0464-a6e5-420f-9e38-ca514e5634ce", - "metadata": {}, - "outputs": [], - "source": [ - "# NOTE: we can use `assistant_id` UUID from the above response, or just pass graph ID instead when creating runs. we'll use graph ID here\n", - "assistant_id = \"agent\"" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "56aa5159-5583-4134-9210-709b969bda6f", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'thread_id': '5fc20631-47b7-48cd-8aa2-9f2eace9778d',\n", - " 'created_at': '2024-06-21T14:58:02.079462+00:00',\n", - " 'updated_at': '2024-06-21T14:58:02.079462+00:00',\n", - " 'metadata': {}}" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# Create a new thread\n", - "thread = await client.threads.create()\n", - "thread" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "147c3f98-f889-4f05-a090-6b31f2a0b291", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[]" - ] - }, - "execution_count": 5, - "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": 6, - "id": "8c7b44ef-4816-496d-88a1-2f7327cf576d", - "metadata": {}, - "outputs": [], - "source": [ - "# Let's kick off a run\n", - "input = {\"messages\": [{\"role\": \"human\", \"content\": \"what's the weather in sf\"}]}\n", - "run = await client.runs.create(thread[\"thread_id\"], assistant_id, input=input)" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "d84b4d80-b0aa-4d9f-a05d-0744b2fe8f72", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'run_id': '1ef2fdea-814c-6165-8b2a-a40e2a028198',\n", - " 'thread_id': '5fc20631-47b7-48cd-8aa2-9f2eace9778d',\n", - " 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca',\n", - " 'created_at': '2024-06-21T14:58:02.095911+00:00',\n", - " 'updated_at': '2024-06-21T14:58:02.095911+00:00',\n", - " 'metadata': {},\n", - " 'status': 'pending',\n", - " 'kwargs': {'input': {'messages': [{'role': 'human',\n", - " 'content': 'what's the weather in sf'}]},\n", - " 'config': {'metadata': {'created_by': 'system'},\n", - " 'configurable': {'run_id': '1ef2fdea-814c-6165-8b2a-a40e2a028198',\n", - " 'user_id': '',\n", - " 'graph_id': 'agent',\n", - " 'thread_id': '5fc20631-47b7-48cd-8aa2-9f2eace9778d',\n", - " 'thread_ts': None,\n", - " 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}},\n", - " 'webhook': None,\n", - " 'temporary': False,\n", - " 'stream_mode': ['events'],\n", - " 'feedback_keys': None,\n", - " 'interrupt_after': None,\n", - " 'interrupt_before': None},\n", - " 'multitask_strategy': 'reject'}" - ] - }, - "execution_count": 7, - "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": 8, - "id": "3639da3c-bfe5-454c-ab1e-8ed7af394dfe", - "metadata": {}, - "outputs": [], - "source": [ - "# Wait until the run finishes\n", - "await client.runs.join(thread[\"thread_id\"], run[\"run_id\"])" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "id": "8fa206ed-515e-4607-9a80-bebafe76cc24", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'run_id': '1ef2fdea-814c-6165-8b2a-a40e2a028198',\n", - " 'thread_id': '5fc20631-47b7-48cd-8aa2-9f2eace9778d',\n", - " 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca',\n", - " 'created_at': '2024-06-21T14:58:02.095911+00:00',\n", - " 'updated_at': '2024-06-21T14:58:02.095911+00:00',\n", - " 'metadata': {},\n", - " 'status': 'success',\n", - " 'kwargs': {'input': {'messages': [{'role': 'human',\n", - " 'content': 'what's the weather in sf'}]},\n", - " 'config': {'metadata': {'created_by': 'system'},\n", - " 'configurable': {'run_id': '1ef2fdea-814c-6165-8b2a-a40e2a028198',\n", - " 'user_id': '',\n", - " 'graph_id': 'agent',\n", - " 'thread_id': '5fc20631-47b7-48cd-8aa2-9f2eace9778d',\n", - " 'thread_ts': None,\n", - " 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}},\n", - " 'webhook': None,\n", - " 'temporary': False,\n", - " 'stream_mode': ['events'],\n", - " 'feedback_keys': None,\n", - " 'interrupt_after': None,\n", - " 'interrupt_before': None},\n", - " 'multitask_strategy': 'reject'}" - ] - }, - "execution_count": 9, - "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": 10, - "id": "8de4495f-7873-487c-b1a8-ad2a78a1ff35", - "metadata": {}, - "outputs": [], - "source": [ - "# We can get the final results\n", - "final_result = await client.threads.get_state(thread[\"thread_id\"])" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "id": "9da76fce-66e4-4f1b-8c24-09759889e50e", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'values': {'messages': [{'content': 'what's the weather in sf',\n", - " 'additional_kwargs': {},\n", - " 'response_metadata': {},\n", - " 'type': 'human',\n", - " 'name': None,\n", - " 'id': 'bfe07fff-cb40-40be-84d5-a061d2c40006',\n", - " 'example': False},\n", - " {'content': [{'id': 'toolu_01QUzhhfDQkpbPSediUrXvQb',\n", - " 'input': {'query': 'weather in san francisco'},\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-6d8665ca-a77d-4b44-9a7b-4e975b155fb1',\n", - " 'example': False,\n", - " 'tool_calls': [{'name': 'tavily_search_results_json',\n", - " 'args': {'query': 'weather in san francisco'},\n", - " 'id': 'toolu_01QUzhhfDQkpbPSediUrXvQb'}],\n", - " 'invalid_tool_calls': [],\n", - " 'usage_metadata': None},\n", - " {'content': '[{\"url\": \"https://www.timeanddate.com/weather/usa/san-francisco/historic\", \"content\": \"San Francisco Weather History for the Previous 24 Hours Show weather for: Previous 24 hours June 17, 2024 June 16, 2024 June 15, 2024 June 14, 2024 June 13, 2024 June 12, 2024 June 11, 2024 June 10, 2024 June 9, 2024 June 8, 2024 June 7, 2024 June 6, 2024 June 5, 2024 June 4, 2024 June 3, 2024 June 2, 2024\"}]',\n", - " 'additional_kwargs': {},\n", - " 'response_metadata': {},\n", - " 'type': 'tool',\n", - " 'name': 'tavily_search_results_json',\n", - " 'id': '257a1f29-2f66-4f9e-b35d-c8818dbbaa3f',\n", - " 'tool_call_id': 'toolu_01QUzhhfDQkpbPSediUrXvQb'},\n", - " {'content': [{'text': 'The search results provide historic weather data for San Francisco, but do not give the current weather conditions. To get the current weather forecast for San Francisco, I would need to refine my search query. Here is an updated search:',\n", - " 'type': 'text'},\n", - " {'id': 'toolu_01RLJEcWYRvRoBhiHdrhoRZx',\n", - " 'input': {'query': 'san francisco weather forecast today'},\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-ca41dbf8-7e89-4ff2-a245-87098d7928ba',\n", - " 'example': False,\n", - " 'tool_calls': [{'name': 'tavily_search_results_json',\n", - " 'args': {'query': 'san francisco weather forecast today'},\n", - " 'id': 'toolu_01RLJEcWYRvRoBhiHdrhoRZx'}],\n", - " 'invalid_tool_calls': [],\n", - " 'usage_metadata': None},\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\\': 1718981382, \\'localtime\\': \\'2024-06-21 7:49\\'}, \\'current\\': {\\'last_updated_epoch\\': 1718981100, \\'last_updated\\': \\'2024-06-21 07:45\\', \\'temp_c\\': 12.8, \\'temp_f\\': 55.0, \\'is_day\\': 1, \\'condition\\': {\\'text\\': \\'Overcast\\', \\'icon\\': \\'//cdn.weatherapi.com/weather/64x64/day/122.png\\', \\'code\\': 1009}, \\'wind_mph\\': 6.9, \\'wind_kph\\': 11.2, \\'wind_degree\\': 200, \\'wind_dir\\': \\'SSW\\', \\'pressure_mb\\': 1011.0, \\'pressure_in\\': 29.84, \\'precip_mm\\': 0.01, \\'precip_in\\': 0.0, \\'humidity\\': 86, \\'cloud\\': 100, \\'feelslike_c\\': 12.2, \\'feelslike_f\\': 53.9, \\'windchill_c\\': 11.2, \\'windchill_f\\': 52.1, \\'heatindex_c\\': 12.0, \\'heatindex_f\\': 53.5, \\'dewpoint_c\\': 9.4, \\'dewpoint_f\\': 48.8, \\'vis_km\\': 16.0, \\'vis_miles\\': 9.0, \\'uv\\': 3.0, \\'gust_mph\\': 7.6, \\'gust_kph\\': 12.2}}\"}]',\n", - " 'additional_kwargs': {},\n", - " 'response_metadata': {},\n", - " 'type': 'tool',\n", - " 'name': 'tavily_search_results_json',\n", - " 'id': 'c80a3720-6a9f-4ff0-9ce2-6112e66a6f81',\n", - " 'tool_call_id': 'toolu_01RLJEcWYRvRoBhiHdrhoRZx'},\n", - " {'content': 'The updated search provides the current weather forecast for San Francisco. According to the results, as of 7:49am on June 21, 2024 in San Francisco, the temperature is 55°F (12.8°C), it is overcast with 100% cloud cover, and there are light winds from the south-southwest around 7 mph (11 km/h). The forecast also shows low precipitation of 0.01 mm, high humidity of 86%, and visibility of 9 miles (16 km).\\n\\nIn summary, the current weather in San Francisco is cool, overcast, and breezy based on this weather forecast data. Let me know if you need any other details!',\n", - " 'additional_kwargs': {},\n", - " 'response_metadata': {},\n", - " 'type': 'ai',\n", - " 'name': None,\n", - " 'id': 'run-4f23b53d-a8ec-4038-b3ed-08b2560bf81c',\n", - " 'example': False,\n", - " 'tool_calls': [],\n", - " 'invalid_tool_calls': [],\n", - " 'usage_metadata': None}]},\n", - " 'next': [],\n", - " 'config': {'configurable': {'thread_id': '5fc20631-47b7-48cd-8aa2-9f2eace9778d',\n", - " 'thread_ts': '1ef2fdea-f879-65a5-8005-443b6a4039aa'}},\n", - " 'metadata': {'step': 5,\n", - " 'run_id': '1ef2fdea-814c-6165-8b2a-a40e2a028198',\n", - " 'source': 'loop',\n", - " 'writes': {'agent': {'messages': [{'id': 'run-4f23b53d-a8ec-4038-b3ed-08b2560bf81c',\n", - " 'name': None,\n", - " 'type': 'ai',\n", - " 'content': 'The updated search provides the current weather forecast for San Francisco. According to the results, as of 7:49am on June 21, 2024 in San Francisco, the temperature is 55°F (12.8°C), it is overcast with 100% cloud cover, and there are light winds from the south-southwest around 7 mph (11 km/h). The forecast also shows low precipitation of 0.01 mm, high humidity of 86%, and visibility of 9 miles (16 km).\\n\\nIn summary, the current weather in San Francisco is cool, overcast, and breezy based on this weather forecast data. Let me know if you need any other details!',\n", - " 'example': False,\n", - " 'tool_calls': [],\n", - " 'usage_metadata': None,\n", - " 'additional_kwargs': {},\n", - " 'response_metadata': {},\n", - " 'invalid_tool_calls': []}]}},\n", - " 'user_id': '',\n", - " 'graph_id': 'agent',\n", - " 'thread_id': '5fc20631-47b7-48cd-8aa2-9f2eace9778d',\n", - " 'created_by': 'system',\n", - " 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'},\n", - " 'created_at': '2024-06-21T14:58:14.591805+00:00',\n", - " 'parent_config': {'configurable': {'thread_id': '5fc20631-47b7-48cd-8aa2-9f2eace9778d',\n", - " 'thread_ts': '1ef2fdea-d44c-6fc4-8004-d2713436777d'}}}" - ] - }, - "execution_count": 11, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "final_result" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "id": "ddd6e698-4609-4389-b84a-bb8939fff08b", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'The updated search provides the current weather forecast for San Francisco. According to the results, as of 7:49am on June 21, 2024 in San Francisco, the temperature is 55°F (12.8°C), it is overcast with 100% cloud cover, and there are light winds from the south-southwest around 7 mph (11 km/h). The forecast also shows low precipitation of 0.01 mm, high humidity of 86%, and visibility of 9 miles (16 km).\\n\\nIn summary, the current weather in San Francisco is cool, overcast, and breezy based on this weather forecast data. Let me know if you need any other details!'" - ] - }, - "execution_count": 12, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# We can get the content of the final message\n", - "final_result[\"values\"][\"messages\"][-1][\"content\"]" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "langgraph-example-dev", - "language": "python", - "name": "langgraph-example-dev" - }, - "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": 5 -} diff --git a/examples/cloud_examples/configuration_cloud.ipynb b/examples/cloud_examples/configuration_cloud.ipynb deleted file mode 100644 index e6d29170b..000000000 --- a/examples/cloud_examples/configuration_cloud.ipynb +++ /dev/null @@ -1,198 +0,0 @@ -{ - "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(\n", - " assistant_id=base_assistant[\"assistant_id\"]\n", - ")\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(\n", - " graph_id=\"agent\", config={\"configurable\": {\"model_name\": \"openai\"}}\n", - ")" - ] - }, - { - "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(\n", - " thread[\"thread_id\"], assistant[\"assistant_id\"], input=input\n", - "):\n", - " print(event)" - ] - } - ], - "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/cron_jobs.ipynb b/examples/cloud_examples/cron_jobs.ipynb deleted file mode 100644 index 98b6a1b73..000000000 --- a/examples/cloud_examples/cron_jobs.ipynb +++ /dev/null @@ -1,132 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Cron Jobs\n", - "\n", - "Sometimes you don't want to run your graph based on user interaction, but rather you would like to schedule your graph to run on a schedule - for example if you wish for your graph to compose and send out a weekly email of to-dos for your team. LangGraph Cloud allows you to do this without having to write your own script by using the `Crons` client. To schedule a graph job, you need to pass a [cron expression](https://crontab.cronhub.io/) to inform the client when you want to run the graph. `Cron` jobs are run in the background and do not interfere with normal invocations of the graph.\n", - "\n", - "## Setup\n", - "\n", - "First, let's setup our SDK client, assistant, and thread:" - ] - }, - { - "cell_type": "code", - "execution_count": 110, - "metadata": {}, - "outputs": [], - "source": [ - "from langgraph_sdk import get_client\n", - "\n", - "client = get_client()\n", - "assistants = await client.assistants.search()\n", - "assistants = [a for a in assistants if not a[\"config\"]]\n", - "assistant = assistants[0]\n", - "thread = await client.threads.create()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Cron job on a thread \n", - "\n", - "To create a cron job associated with a specific thread, you can write:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# This schedules a job to run at 15:27 (3:27PM) every day\n", - "cron_1 = await client.crons.create_for_thread(\n", - " thread[\"thread_id\"],\n", - " assistant[\"assistant_id\"],\n", - " schedule=\"27 15 * * *\",\n", - " input={\"messages\": [{\"role\": \"user\", \"content\": \"What time is it?\"}]},\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Note that it is **very** important to delete `Cron` jobs that are no longer useful. Otherwise you could rack up unwanted API charges to the LLM! You can delete a `Cron` job using the following code:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "await client.crons.delete(cron_1[\"cron_id\"])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Cron job stateless\n", - "\n", - "You can also create stateless cron jobs by using the following code:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# This schedules a job to run at 15:27 (3:27PM) every day\n", - "cron_2 = await client.crons.create(\n", - " assistant[\"assistant_id\"],\n", - " schedule=\"27 15 * * *\",\n", - " input={\"messages\": [{\"role\": \"user\", \"content\": \"What time is it?\"}]},\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Again, remember to delete your job once you are done with it!" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "await client.crons.delete(cron_2[\"cron_id\"])" - ] - } - ], - "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 -} diff --git a/examples/cloud_examples/img/webhook_results.png b/examples/cloud_examples/img/webhook_results.png deleted file mode 100644 index 81b9fe703..000000000 Binary files a/examples/cloud_examples/img/webhook_results.png and /dev/null differ diff --git a/examples/cloud_examples/same-thread.ipynb b/examples/cloud_examples/same-thread.ipynb deleted file mode 100644 index 0fe171bef..000000000 --- a/examples/cloud_examples/same-thread.ipynb +++ /dev/null @@ -1,192 +0,0 @@ -{ - "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 Cloud, a thread is not explicitly associated with a particular agent.\n", - "This means that you can run multiple agents on the same thread, which allows a different\n", - "agent to continue from an initial agent's progress.\n", - "\n", - "In this example, we will create two agents and then call them both on the same thread.\n", - "You'll see that the second agent will respond using information from the [checkpoint](https://langchain-ai.github.io/langgraph/concepts/low_level/#checkpointer-state) generated in the thread\n", - "by the first agent as context." - ] - }, - { - "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(\n", - " graph_id=\"agent\", config={\"configurable\": {\"model_name\": \"openai\"}}\n", - ")\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 the OpenAI assistant on the thread 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(\n", - " thread[\"thread_id\"],\n", - " openai_assistant[\"assistant_id\"],\n", - " input=input,\n", - " stream_mode=\"updates\",\n", - "):\n", - " print(event)" - ] - }, - { - "cell_type": "markdown", - "id": "c53709e9-ddb2-4429-9042-456eb6c91244", - "metadata": {}, - "source": [ - "Now, we can run it on a second Anthropic-based assistant and see that this second assistant is aware of the initial question, and can answer the question, `and you?`:" - ] - }, - { - "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(\n", - " thread[\"thread_id\"],\n", - " default_assistant[\"assistant_id\"],\n", - " input=input,\n", - " stream_mode=\"updates\",\n", - "):\n", - " print(event)" - ] - } - ], - "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/stateless_runs.ipynb b/examples/cloud_examples/stateless_runs.ipynb deleted file mode 100644 index 5aa704e35..000000000 --- a/examples/cloud_examples/stateless_runs.ipynb +++ /dev/null @@ -1,152 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Stateless Runs\n", - "\n", - "Most of the time, you provide a `thread_id` to your client when you run your graph in order to keep track of prior runs through the persistent state implemented in LangGraph Cloud. However, if you have your own database to save runs and don't need to use the built in persistent state, you can create stateless runs.\n", - "\n", - "## Setup\n", - "\n", - "First, let's setup our client" - ] - }, - { - "cell_type": "code", - "execution_count": 106, - "metadata": {}, - "outputs": [], - "source": [ - "from langgraph_sdk import get_client\n", - "\n", - "client = get_client()\n", - "assistants = await client.assistants.search()\n", - "assistants = [a for a in assistants if not a[\"config\"]]\n", - "assistant = assistants[0]\n", - "thread = await client.threads.create()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Stateless streaming\n", - "\n", - "We can stream the results of a stateless run in an almost identical fashion to how we stream from a run with the state attribute, but instead of passing a value to the `thread_id` parameter, we pass `None`:" - ] - }, - { - "cell_type": "code", - "execution_count": 107, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "{'agent': {'messages': [{'content': \"Hello Bagatur! It's nice to meet you. Thank you for introducing yourself and sharing your age. Is there anything specific you'd like to know or discuss? I'm here to help with any questions or topics you're interested in.\", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-489ec573-1645-4ce2-a3b8-91b391d50a71', '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", - "\n", - "async for chunk in client.runs.stream(\n", - " # Don't pass in a thread_id and the stream will be stateless\n", - " None,\n", - " assistant[\"assistant_id\"], # graph_id\n", - " input=input,\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": [ - "## Waiting for stateless results\n", - "\n", - "In addition to streaming, you can also wait for a stateless result by using the `.wait` function like follows:" - ] - }, - { - "cell_type": "code", - "execution_count": 108, - "metadata": {}, - "outputs": [], - "source": [ - "stateless_run_result = await client.runs.wait(\n", - " None,\n", - " assistant[\"assistant_id\"], # graph_id\n", - " input=input,\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 109, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'messages': [{'content': 'Hello! My name is Bagatur and I am 26 years old.',\n", - " 'additional_kwargs': {},\n", - " 'response_metadata': {},\n", - " 'type': 'human',\n", - " 'name': None,\n", - " 'id': '5e088543-62c2-43de-9d95-6086ad7f8b48',\n", - " 'example': False},\n", - " {'content': \"Hello Bagatur! It's nice to meet you. Thank you for introducing yourself and sharing your age. Is there anything specific you'd like to know or discuss? I'm here to help with any questions or topics you'd like to explore.\",\n", - " 'additional_kwargs': {},\n", - " 'response_metadata': {},\n", - " 'type': 'ai',\n", - " 'name': None,\n", - " 'id': 'run-d6361e8d-4d4c-45bd-ba47-39520257f773',\n", - " 'example': False,\n", - " 'tool_calls': [],\n", - " 'invalid_tool_calls': [],\n", - " 'usage_metadata': None}]}" - ] - }, - "execution_count": 109, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "stateless_run_result" - ] - } - ], - "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 -} diff --git a/examples/cloud_examples/webhooks.ipynb b/examples/cloud_examples/webhooks.ipynb deleted file mode 100644 index 682bcd8aa..000000000 --- a/examples/cloud_examples/webhooks.ipynb +++ /dev/null @@ -1,72 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Use Webhooks\n", - "\n", - "You may wish to use webhooks in your client, especially when using async streams in case you want to update something in your service once the API call to LangGraph Cloud has finished running. To do so, you will need to expose an endpoint that can accept POST requests, and then pass it to your API request in the \"webhook\" parameter.\n", - "\n", - "Currently, the SDK has not exposed this endpoint but you can access it through curl commands as follows.\n", - "\n", - "The following endpoints accept `webhook` as a parameter: \n", - "\n", - "- Create Run -> POST /thread/{thread_id}/runs\n", - "- Create Thread Cron -> POST /thread/{thread_id}/runs/crons\n", - "- Stream Run -> POST /thread/{thread_id}/runs/stream\n", - "- Wait Run -> POST /thread/{thread_id}/runs/wait\n", - "- Create Cron -> POST /runs/crons\n", - "- Stream Run Stateless -> POST /runs/stream\n", - "- Wait Run Stateless -> POST /runs/wait\n", - "\n", - "The following example uses a url from a public website that allows users to create free webhooks, but you should pass in the webhook that you wish to use. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "curl --request POST \\\n", - " --url http://localhost:8123/threads/b76d1e94-f251-40e3-8933-796d775cdb4c/runs/stream \\\n", - " --header 'Content-Type: application/json' \\\n", - " --data '{\n", - " \"assistant_id\": \"fe096781-5601-53d2-b2f6-0d3403f7e9ca\",\n", - " \"input\" : {\"messages\":[{\"role\": \"user\", \"content\": \"Hello! My name is Bagatur and I am 26 years old.\"}]},\n", - " \"metadata\": {},\n", - " \"config\": {\n", - " \"configurable\": {}\n", - " },\n", - " \"multitask_strategy\": \"reject\",\n", - " \"stream_mode\": [\n", - " \"values\"\n", - " ],\n", - " \"webhook\": \"https://webhook.site/6ca33471-dd65-4103-a851-0a252dae0f2a\"\n", - "}'" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "To check that this worked as intended, we can go to the website where our webhook was created and confirm that it received a POST request:" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "![Webhook response](./img/webhook_results.png)" - ] - } - ], - "metadata": { - "language_info": { - "name": "python" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -}