Deprecate Chat agent executor & Function Calling Executor in Docs (#392)

This commit is contained in:
William FH
2024-05-04 00:17:54 -07:00
committed by GitHub
parent 119bbe6bae
commit 21b8cbfd33
17 changed files with 877 additions and 769 deletions
+30
View File
@@ -2,3 +2,33 @@
::: langgraph.checkpoint
handler: python
### BaseCheckpointSaver
::: langgraph.checkpoint.base.BaseCheckpointSaver
handler: python
## Implementations
LangGraph also natively provides the following checkpoint implementations.
### AsyncSqliteSaver
::: langgraph.checkpoint.aiosqlite.AsyncSqliteSaver
handler: python
### SqliteSaver
::: langgraph.checkpoint.sqlite.SqliteSaver
handler: python
members:
- put
- list
- get_tuple
### MemorySaver
::: langgraph.checkpoint.memory.MemorySaver
handler: python
+3 -4
View File
@@ -1,5 +1,7 @@
# Graph Definitions
Graphs are the core abstraction of LangGraph. Each [StateGraph](#langgraph.graph.StateGraph) implementation is used to create graph workflows. Once compiled, you can run the [CompiledGraph](#compiledgraph) to run the application.
::: langgraph.graph
handler: python
@@ -7,9 +9,6 @@
::: langgraph.graph.graph.CompiledGraph
handler: python
members:
- get_graph
- invoke
## MessageGraph
@@ -19,4 +18,4 @@
## add_messages
::: ::: langgraph.graph.message.add_messages
::: langgraph.graph.message.add_messages
+1 -10
View File
@@ -37,16 +37,7 @@ from langgraph.prebuilt import ToolInvocation
from langgraph.prebuilt.chat_agent_executor import create_tool_calling_executor
```
::: langgraph.prebuilt.chat_agent_executor
## `create_agent_executor`
```python
from langgraph.prebuilt import create_agent_executor
```
::: langgraph.prebuilt.create_agent_executor
::: langgraph.prebuilt.chat_agent_executor.create_tool_calling_executor
## `tools_condition`
+3 -336
View File
@@ -5,344 +5,11 @@
"id": "f961801a-6025-4b73-be3b-c3a8a75d4167",
"metadata": {},
"source": [
"# Agent Executor\n",
"# (Deprecated) Agent Executor\n",
"\n",
"This notebook walks through an example creating an agent executor to work with an existing LangChain agent.\n",
"This is useful for getting started quickly.\n",
"However, it is highly likely you will want to customize the logic - for information on that, check out the other examples in this folder."
"The `create_agent_executor` function is deprecated in favor of [create_tool_calling_executor](../chat_agent_executor_with_function_calling/high-level-tools.ipynb).\n",
"This was done to better align with the underlying model providers' migration from \"function calling\" to \"tool calling\", which typically supports parallel tool usage."
]
},
{
"cell_type": "markdown",
"id": "e6dd032b-bfe9-458c-a8ef-a14c78e0ad3f",
"metadata": {},
"source": [
"## Setup\n",
"\n",
"First we need to install the packages required"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "1759bc06-8af3-4b73-abbf-0be3fa4c31fb",
"metadata": {},
"outputs": [],
"source": [
"%%capture --no-stderr\n",
"%pip install --quiet -U langchain langchain_openai tavily-python"
]
},
{
"cell_type": "markdown",
"id": "fa08bd1a-efaa-46f5-adf8-47a84f738381",
"metadata": {},
"source": [
"Next, we need to set API keys for OpenAI (the LLM we will use) and Tavily (the search tool we will use)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "eb8e51dc-028b-4ea5-9847-f22fcbed6dac",
"metadata": {},
"outputs": [],
"source": [
"import os\n",
"import getpass\n",
"\n",
"os.environ[\"OPENAI_API_KEY\"] = getpass.getpass(\"OpenAI API Key:\")\n",
"os.environ[\"TAVILY_API_KEY\"] = getpass.getpass(\"Tavily API Key:\")"
]
},
{
"cell_type": "markdown",
"id": "9242c0d7-b1da-41a0-9a3e-ed3afab3528e",
"metadata": {},
"source": [
"Optionally, we can set API key for [LangSmith tracing](https://smith.langchain.com/), which will give us best-in-class observability."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "5db4438c-7802-4050-9dd9-14a6cac21a91",
"metadata": {},
"outputs": [],
"source": [
"os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n",
"os.environ[\"LANGCHAIN_API_KEY\"] = getpass.getpass(\"LangSmith API Key:\")"
]
},
{
"cell_type": "markdown",
"id": "6ae180d9-abd3-4a44-8fb1-a2c89434fbeb",
"metadata": {},
"source": [
"## Set up LangChain Agent\n",
"\n",
"First, will set up our LangChain Agent. \n",
"See documentation [here](https://python.langchain.com/docs/modules/agents/) for more information on what these agents are and how to think about them"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "e2fdcac4-d134-402b-b423-b0cf4b939f5d",
"metadata": {},
"outputs": [],
"source": [
"from langchain_openai import ChatOpenAI\n",
"from langchain import hub\n",
"from langchain.agents import create_openai_functions_agent\n",
"from langchain_community.tools.tavily_search import TavilySearchResults"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "afb59979-c7a3-435f-b147-f8d501f6ff13",
"metadata": {},
"outputs": [],
"source": [
"tools = [TavilySearchResults(max_results=1)]\n",
"\n",
"# Get the prompt to use - you can modify this!\n",
"prompt = hub.pull(\"hwchase17/openai-functions-agent\")\n",
"\n",
"# Choose the LLM that will drive the agent\n",
"llm = ChatOpenAI(model=\"gpt-3.5-turbo-1106\")\n",
"\n",
"# Construct the OpenAI Functions agent\n",
"agent_runnable = create_openai_functions_agent(llm, tools, prompt)"
]
},
{
"cell_type": "markdown",
"id": "0bcb5ff8-b2d1-4fb2-bed4-3726f96db772",
"metadata": {},
"source": [
"## Create agent executor\n",
"\n",
"Now we will use the high level method to create the agent executor"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "7a138eb4-a469-4b30-a059-99d6ea944648",
"metadata": {},
"outputs": [],
"source": [
"from langgraph.prebuilt import create_agent_executor"
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "9be722f0-c9ab-4bd2-af27-66adf51134d2",
"metadata": {},
"outputs": [],
"source": [
"app = create_agent_executor(agent_runnable, tools)"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "019b591b-fd71-4ee8-ae94-06d0e2dc6a4d",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{'agent_outcome': AgentActionMessageLog(tool='tavily_search_results_json', tool_input={'query': 'current weather in San Francisco'}, log=\"\\nInvoking: `tavily_search_results_json` with `{'query': 'current weather in San Francisco'}`\\n\\n\\n\", message_log=[AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\"query\":\"current weather in San Francisco\"}', 'name': 'tavily_search_results_json'}})])}\n",
"----\n",
"{'intermediate_steps': [(AgentActionMessageLog(tool='tavily_search_results_json', tool_input={'query': 'current weather in San Francisco'}, log=\"\\nInvoking: `tavily_search_results_json` with `{'query': 'current weather in San Francisco'}`\\n\\n\\n\", message_log=[AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\"query\":\"current weather in San Francisco\"}', 'name': 'tavily_search_results_json'}})]), \"[{'url': 'https://en.climate-data.org/north-america/united-states-of-america/california/san-francisco-385/t/january-1/', 'content': 'San Francisco Weather in January you can find all information about the weather in San Francisco in January: San Francisco weather in January San Francisco weather by month // weather averages 9.6 (49.2) 6.2 (43.2) 14 (57.3) 113 San Francisco weather in January // weather averages Airport close to San FranciscoWeather ☀ ⛅ San Francisco ☀ ⛅ January ☀ ⛅ Information on temperature, sunshine hours, water temperature & rainfall in January for San Francisco. ... Are you planning a holiday with hopefully nice weather in San Francisco in January 2024? Here you can find all information about the weather in San Francisco in January: ... 15. January ...'}]\")]}\n",
"----\n",
"{'agent_outcome': AgentFinish(return_values={'output': \"I couldn't find the current weather in San Francisco. However, you can visit a reliable weather website or check a weather app for the most up-to-date information.\"}, log=\"I couldn't find the current weather in San Francisco. However, you can visit a reliable weather website or check a weather app for the most up-to-date information.\")}\n",
"----\n",
"{'input': 'what is the weather in sf', 'chat_history': [], 'agent_outcome': AgentFinish(return_values={'output': \"I couldn't find the current weather in San Francisco. However, you can visit a reliable weather website or check a weather app for the most up-to-date information.\"}, log=\"I couldn't find the current weather in San Francisco. However, you can visit a reliable weather website or check a weather app for the most up-to-date information.\"), 'intermediate_steps': [(AgentActionMessageLog(tool='tavily_search_results_json', tool_input={'query': 'current weather in San Francisco'}, log=\"\\nInvoking: `tavily_search_results_json` with `{'query': 'current weather in San Francisco'}`\\n\\n\\n\", message_log=[AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\"query\":\"current weather in San Francisco\"}', 'name': 'tavily_search_results_json'}})]), \"[{'url': 'https://en.climate-data.org/north-america/united-states-of-america/california/san-francisco-385/t/january-1/', 'content': 'San Francisco Weather in January you can find all information about the weather in San Francisco in January: San Francisco weather in January San Francisco weather by month // weather averages 9.6 (49.2) 6.2 (43.2) 14 (57.3) 113 San Francisco weather in January // weather averages Airport close to San FranciscoWeather ☀ ⛅ San Francisco ☀ ⛅ January ☀ ⛅ Information on temperature, sunshine hours, water temperature & rainfall in January for San Francisco. ... Are you planning a holiday with hopefully nice weather in San Francisco in January 2024? Here you can find all information about the weather in San Francisco in January: ... 15. January ...'}]\")]}\n",
"----\n"
]
}
],
"source": [
"inputs = {\"input\": \"what is the weather in sf\", \"chat_history\": []}\n",
"for s in app.stream(inputs):\n",
" print(list(s.values())[0])\n",
" print(\"----\")"
]
},
{
"cell_type": "code",
"execution_count": 18,
"id": "c6a664cd-083e-4d85-aeaf-501463881f05",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"AgentFinish(return_values={'output': \"I couldn't find the current weather in San Francisco. However, you can check the weather on a reliable weather website or using a weather app for the most up-to-date information.\"}, log=\"I couldn't find the current weather in San Francisco. However, you can check the weather on a reliable weather website or using a weather app for the most up-to-date information.\")"
]
},
"execution_count": 18,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"s[\"__end__\"][\"agent_outcome\"]"
]
},
{
"cell_type": "markdown",
"id": "a7bd3e55-ee7e-4276-81bd-39e6131fcf77",
"metadata": {},
"source": [
"## Custom Input Schema\n",
"\n",
"By default, the `create_agent_executor` assumes that the input will be a dictionary with two keys: `input` and `chat_history`. \n",
"If this is not the case, you can easily customize the input schema.\n",
"You should do this, by defining a schema as a TypedDict.\n",
"\n",
"For this example, we will create a new agent that expects `question` and `language` as inputs."
]
},
{
"cell_type": "markdown",
"id": "a98c5ec5-f836-4b3c-b37b-00102b496366",
"metadata": {},
"source": [
"### Create New Agent"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "676841ec-b5a6-495e-a88a-7eb0ab3cbae6",
"metadata": {},
"outputs": [],
"source": [
"from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder\n",
"\n",
"prompt = ChatPromptTemplate.from_messages(\n",
" [\n",
" (\n",
" \"human\",\n",
" \"Respond to the user question: {question}. Answer in this language: {language}\",\n",
" ),\n",
" MessagesPlaceholder(variable_name=\"agent_scratchpad\"),\n",
" ]\n",
")\n",
"agent_runnable = create_openai_functions_agent(llm, tools, prompt)"
]
},
{
"cell_type": "markdown",
"id": "5889d980-d209-447b-8489-1d4873acfdc2",
"metadata": {},
"source": [
"### Define Input Schema"
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "3d1df06d-1564-46a1-a72f-58dfc65927bc",
"metadata": {},
"outputs": [],
"source": [
"from typing import TypedDict"
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "2fdbb687-9c72-42c7-afcb-3f8940f3e5f4",
"metadata": {},
"outputs": [],
"source": [
"class InputSchema(TypedDict):\n",
" question: str\n",
" language: str"
]
},
{
"cell_type": "markdown",
"id": "329bb518-02a8-477c-8898-d04cb64fc460",
"metadata": {},
"source": [
"### Create new agent executor"
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "1ad88990-896d-48d5-bd34-01c9f6a37734",
"metadata": {},
"outputs": [],
"source": [
"app = create_agent_executor(agent_runnable, tools, input_schema=InputSchema)"
]
},
{
"cell_type": "code",
"execution_count": 11,
"id": "13ffa18c-9a9f-4e0e-8298-32aeff94ce5d",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{'agent_outcome': AgentActionMessageLog(tool='tavily_search_results_json', tool_input={'query': 'che tempo fa a sf'}, log=\"\\nInvoking: `tavily_search_results_json` with `{'query': 'che tempo fa a sf'}`\\n\\n\\n\", message_log=[AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\"query\":\"che tempo fa a sf\"}', 'name': 'tavily_search_results_json'}})])}\n",
"----\n",
"{'intermediate_steps': [(AgentActionMessageLog(tool='tavily_search_results_json', tool_input={'query': 'che tempo fa a sf'}, log=\"\\nInvoking: `tavily_search_results_json` with `{'query': 'che tempo fa a sf'}`\\n\\n\\n\", message_log=[AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\"query\":\"che tempo fa a sf\"}', 'name': 'tavily_search_results_json'}})]), '[{\\'url\\': \\'https://www.mxbars.net/2024/01/14/san-francisco-supercross-2024-results-and-points-video/\\', \\'content\\': \"Scritto domenica 14 Gennaio 2024 alle 04:38. SAN FRANCISCO Oracle Park, CA January 13, 2024 sera gli orari sono anticipati di due ore causa mal tempo per non compromettere lo spettacolo! secondo weekend con la seconda tappa e pista molto tecnica per San Francisco dove sta piovendo e lo stadio aperto non . Commenta la gara\\\\xa0CLICCANDO\\\\xa0il link! http://forum.mxbars.net/viewtopic.php?f=18&t=50182SAN FRANCISCO. Oracle Park, CA. January 13, 2024. Ecco che la NUOVA stagione del Monster Energy Supercross 2024 continua, dopo Anaheim 1 si passa al secondo weekend con la seconda tappa e pista molto tecnica per San Francisco dove sta piovendo e lo stadio aperto non vede possibilità di chiudersi, per la lotta nella 450 dove sono tutti agguerriti e quest\\'anno il livello è ancora più alto ...\"}]')]}\n",
"----\n",
"{'agent_outcome': AgentFinish(return_values={'output': 'Al momento sta piovendo a San Francisco.'}, log='Al momento sta piovendo a San Francisco.')}\n",
"----\n",
"{'question': 'what is the weather in sf', 'language': 'italian', 'agent_outcome': AgentFinish(return_values={'output': 'Al momento sta piovendo a San Francisco.'}, log='Al momento sta piovendo a San Francisco.'), 'intermediate_steps': [(AgentActionMessageLog(tool='tavily_search_results_json', tool_input={'query': 'che tempo fa a sf'}, log=\"\\nInvoking: `tavily_search_results_json` with `{'query': 'che tempo fa a sf'}`\\n\\n\\n\", message_log=[AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\"query\":\"che tempo fa a sf\"}', 'name': 'tavily_search_results_json'}})]), '[{\\'url\\': \\'https://www.mxbars.net/2024/01/14/san-francisco-supercross-2024-results-and-points-video/\\', \\'content\\': \"Scritto domenica 14 Gennaio 2024 alle 04:38. SAN FRANCISCO Oracle Park, CA January 13, 2024 sera gli orari sono anticipati di due ore causa mal tempo per non compromettere lo spettacolo! secondo weekend con la seconda tappa e pista molto tecnica per San Francisco dove sta piovendo e lo stadio aperto non . Commenta la gara\\\\xa0CLICCANDO\\\\xa0il link! http://forum.mxbars.net/viewtopic.php?f=18&t=50182SAN FRANCISCO. Oracle Park, CA. January 13, 2024. Ecco che la NUOVA stagione del Monster Energy Supercross 2024 continua, dopo Anaheim 1 si passa al secondo weekend con la seconda tappa e pista molto tecnica per San Francisco dove sta piovendo e lo stadio aperto non vede possibilità di chiudersi, per la lotta nella 450 dove sono tutti agguerriti e quest\\'anno il livello è ancora più alto ...\"}]')]}\n",
"----\n"
]
}
],
"source": [
"inputs = {\"question\": \"what is the weather in sf\", \"language\": \"italian\"}\n",
"for s in app.stream(inputs):\n",
" print(list(s.values())[0])\n",
" print(\"----\")"
]
},
{
"cell_type": "code",
"execution_count": 25,
"id": "fd60f5d6-bd4b-4995-80dd-63f268c17cff",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"AgentFinish(return_values={'output': 'Il clima a San Francisco durante il mese di gennaio è generalmente fresco con temperature medie di circa 9.6°C (49.2°F) e massime di 14°C (57.3°F). Si consiglia di prepararsi a temperature fresche se si pianifica una visita a San Francisco in gennaio.'}, log='Il clima a San Francisco durante il mese di gennaio è generalmente fresco con temperature medie di circa 9.6°C (49.2°F) e massime di 14°C (57.3°F). Si consiglia di prepararsi a temperature fresche se si pianifica una visita a San Francisco in gennaio.')"
]
},
"execution_count": 25,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"s[\"__end__\"][\"agent_outcome\"]"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "20cac1a0-0c51-4cbd-ae27-929d71db2b56",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
@@ -5,111 +5,11 @@
"id": "8bcd1a3d-7c50-4f58-be4e-1ed654aa33be",
"metadata": {},
"source": [
"# Chat Executor: with function calling\n",
"# (Deprecated) Chat Executor: with function calling\n",
"\n",
"This notebook walks through an example creating a chat executor that uses function calling.\n",
"This is useful for getting started quickly.\n",
"However, it is highly likely you will want to customize the logic - for information on that, check out the other examples in this folder."
"The function calling executor is deprecated in favor of [create_tool_calling_executor](../chat_agent_executor_with_function_calling/high-level-tools.ipynb).\n",
"This was done to better align with the underlying model providers' migration from \"function calling\" to \"tool calling\", which typically supports parallel tool usage."
]
},
{
"cell_type": "markdown",
"id": "e130cf70-a30e-47d7-8fd5-464f1a92e374",
"metadata": {},
"source": [
"## Set up the chat model and tools\n",
"\n",
"Here we will define the chat model and tools that we want to use.\n",
"Importantly, this model MUST support OpenAI function calling."
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "efb7e3c0-c63f-40f6-93ce-19681d650fc2",
"metadata": {},
"outputs": [],
"source": [
"from langchain_openai import ChatOpenAI\n",
"from langchain_community.tools.tavily_search import TavilySearchResults\n",
"from langgraph.prebuilt import chat_agent_executor\n",
"from langchain_core.messages import HumanMessage"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "a7025f33-3160-41cf-868b-17ebc916fb1d",
"metadata": {},
"outputs": [],
"source": [
"tools = [TavilySearchResults(max_results=1)]\n",
"model = ChatOpenAI()"
]
},
{
"cell_type": "markdown",
"id": "43064805-2ac9-4b5a-850c-a68dd7282350",
"metadata": {},
"source": [
"## Create executor\n",
"\n",
"We can now use the high level interface to create the executor"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "32b4ae66-f667-4a8b-a602-503fd0effcd9",
"metadata": {},
"outputs": [],
"source": [
"app = chat_agent_executor.create_function_calling_executor(model, tools)"
]
},
{
"cell_type": "markdown",
"id": "d63dbfc7-a5c1-4a03-991c-f0789ba52c52",
"metadata": {},
"source": [
"We can now invoke this executor. The input to this must be a dictionary with a single `messsages` key that contains a list of messages."
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "0abc5655-d772-450c-832f-1fee1111a5f6",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{'messages': [AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\"query\":\"weather in San Francisco\"}', 'name': 'tavily_search_results_json'}})]}\n",
"----\n",
"{'messages': [FunctionMessage(content=\"[{'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.'}]\", name='tavily_search_results_json')]}\n",
"----\n",
"{'messages': [AIMessage(content='You can check the current and future weather conditions for San Francisco, CA on [AccuWeather](https://www.accuweather.com/en/us/san-francisco/94103/weather-forecast/347629).')]}\n",
"----\n",
"{'messages': [HumanMessage(content='what is the weather in sf'), AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\"query\":\"weather in San Francisco\"}', 'name': 'tavily_search_results_json'}}), FunctionMessage(content=\"[{'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.'}]\", name='tavily_search_results_json'), AIMessage(content='You can check the current and future weather conditions for San Francisco, CA on [AccuWeather](https://www.accuweather.com/en/us/san-francisco/94103/weather-forecast/347629).')]}\n",
"----\n"
]
}
],
"source": [
"inputs = {\"messages\": [HumanMessage(content=\"what is the weather in sf\")]}\n",
"for s in app.stream(inputs):\n",
" print(list(s.values())[0])\n",
" print(\"----\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "87f147e3-f96f-4b96-a3cc-ec7affd7a57f",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
@@ -128,7 +28,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.1"
"version": "3.11.2"
}
},
"nbformat": 4,
File diff suppressed because one or more lines are too long
View File
+34
View File
@@ -0,0 +1,34 @@
import functools
import warnings
from typing import Callable, TypeVar
class LangGraphDeprecationWarning(DeprecationWarning):
pass
F = TypeVar("F", bound=Callable)
def deprecated(version: str, alternative: str, *, example: str = ""):
def decorator(func: F) -> F:
@functools.wraps(func)
def wrapper(*args, **kwargs):
message = (
f"{func.__name__} is deprecated as of version {version} and will be"
f" removed in a future version. Use {alternative} instead.{example}"
)
warnings.warn(message, LangGraphDeprecationWarning, stacklevel=2)
return func(*args, **kwargs)
docstring = (
f"**Deprecated**: This function is deprecated as of version {version}. "
f"Use `{alternative}` instead."
)
if func.__doc__:
docstring = docstring + f"\n\n{func.__doc__}"
wrapper.__doc__ = docstring
return wrapper
return decorator
+138 -24
View File
@@ -18,12 +18,61 @@ from langgraph.checkpoint.sqlite import JsonPlusSerializerCompat
class AsyncSqliteSaver(BaseCheckpointSaver, AbstractAsyncContextManager):
"""An asynchronous checkpoint saver that stores checkpoints in a SQLite database.
Note: Requires the `aiosqlite` package. Install it with `pip install aiosqlite`.
Args:
conn (aiosqlite.Connection): The asynchronous SQLite database connection.
serde (Optional[SerializerProtocol]): The serializer to use for serializing and deserializing checkpoints. Defaults to JsonPlusSerializerCompat.
at (Optional[CheckpointAt]): The checkpoint strategy to use. Defaults to None.
Examples:
Usage within a StateGraph:
import asyncio
import aiosqlite
from langgraph.checkpoint.aiosqlite import AsyncSqliteSaver
from langgraph.graph import StateGraph
builder = StateGraph(int)
builder.add_node("add_one", lambda x: x + 1)
builder.set_entry_point("add_one")
builder.set_finish_point("add_one")
memory = AsyncSqliteSaver.from_conn_string("checkpoints.sqlite")
graph = builder.compile(checkpointer=memory)
coro = graph.ainvoke(1, {"configurable": {"thread_id": "thread-1"}})
asyncio.run(coro) # Output: 2
Raw usage:
import asyncio
import aiosqlite
from langgraph.checkpoint.aiosqlite import AsyncSqliteSaver
async def main():
async with aiosqlite.connect("checkpoints.db") as conn:
saver = AsyncSqliteSaver(conn)
config = {"configurable": {"thread_id": "1"}}
checkpoint = {"ts": "2023-05-03T10:00:00Z", "data": {"key": "value"}}
saved_config = await saver.aput(config, checkpoint)
print(
saved_config
) # Output: {"configurable": {"thread_id": "1", "thread_ts": "2023-05-03T10:00:00Z"}}
asyncio.run(main())
"""
serde = JsonPlusSerializerCompat()
conn: aiosqlite.Connection
lock: asyncio.Lock
is_setup: bool
def __init__(
@@ -40,6 +89,14 @@ class AsyncSqliteSaver(BaseCheckpointSaver, AbstractAsyncContextManager):
@classmethod
def from_conn_string(cls, conn_string: str) -> "AsyncSqliteSaver":
"""Create a new AsyncSqliteSaver instance from a connection string.
Args:
conn_string (str): The SQLite connection string.
Returns:
AsyncSqliteSaver: A new AsyncSqliteSaver instance.
"""
return AsyncSqliteSaver(conn=aiosqlite.connect(conn_string))
async def __aenter__(self) -> Self:
@@ -55,6 +112,12 @@ class AsyncSqliteSaver(BaseCheckpointSaver, AbstractAsyncContextManager):
return await self.conn.close()
async def setup(self) -> None:
"""Set up the checkpoint database asynchronously.
This method creates the necessary tables in the SQLite database if they don't
already exist. It is called automatically when needed and should not be called
directly by the user.
"""
async with self.lock:
if self.is_setup:
return
@@ -76,6 +139,19 @@ class AsyncSqliteSaver(BaseCheckpointSaver, AbstractAsyncContextManager):
self.is_setup = True
async def aget_tuple(self, config: RunnableConfig) -> Optional[CheckpointTuple]:
"""Get a checkpoint tuple from the database asynchronously.
This method retrieves a checkpoint tuple from the SQLite database based on the
provided config. If the config contains a "thread_ts" key, the checkpoint with
the matching thread ID and timestamp is retrieved. Otherwise, the latest checkpoint
for the given thread ID is retrieved.
Args:
config (RunnableConfig): The config to use for retrieving the checkpoint.
Returns:
Optional[CheckpointTuple]: The retrieved checkpoint tuple, or None if no matching checkpoint was found.
"""
await self.setup()
if config["configurable"].get("thread_ts"):
async with self.conn.execute(
@@ -89,14 +165,16 @@ class AsyncSqliteSaver(BaseCheckpointSaver, AbstractAsyncContextManager):
return CheckpointTuple(
config,
self.serde.loads(value[0]),
{
"configurable": {
"thread_id": config["configurable"]["thread_id"],
"thread_ts": value[1],
(
{
"configurable": {
"thread_id": config["configurable"]["thread_id"],
"thread_ts": value[1],
}
}
}
if value[1]
else None,
if value[1]
else None
),
)
else:
async with self.conn.execute(
@@ -112,14 +190,16 @@ class AsyncSqliteSaver(BaseCheckpointSaver, AbstractAsyncContextManager):
}
},
self.serde.loads(value[3]),
{
"configurable": {
"thread_id": value[0],
"thread_ts": value[2],
(
{
"configurable": {
"thread_id": value[0],
"thread_ts": value[2],
}
}
}
if value[2]
else None,
if value[2]
else None
),
)
async def alist(
@@ -129,6 +209,19 @@ class AsyncSqliteSaver(BaseCheckpointSaver, AbstractAsyncContextManager):
before: Optional[RunnableConfig] = None,
limit: Optional[int] = None,
) -> AsyncIterator[CheckpointTuple]:
"""List checkpoints from the database asynchronously.
This method retrieves a list of checkpoint tuples from the SQLite database based
on the provided config. The checkpoints are ordered by timestamp in descending order.
Args:
config (RunnableConfig): The config to use for listing the checkpoints.
before (Optional[RunnableConfig]): If provided, only checkpoints before the specified timestamp are returned. Defaults to None.
limit (Optional[int]): The maximum number of checkpoints to return. Defaults to None.
Yields:
AsyncIterator[CheckpointTuple]: An asynchronous iterator of checkpoint tuples.
"""
await self.setup()
query = (
"SELECT thread_id, thread_ts, parent_ts, checkpoint FROM checkpoints WHERE thread_id = ? ORDER BY thread_ts DESC"
@@ -139,25 +232,46 @@ class AsyncSqliteSaver(BaseCheckpointSaver, AbstractAsyncContextManager):
query += f" LIMIT {limit}"
async with self.conn.execute(
query,
(str(config["configurable"]["thread_id"]),)
if before is None
else (
str(config["configurable"]["thread_id"]),
str(before["configurable"]["thread_ts"]),
(
(str(config["configurable"]["thread_id"]),)
if before is None
else (
str(config["configurable"]["thread_id"]),
str(before["configurable"]["thread_ts"]),
)
),
) as cursor:
async for thread_id, thread_ts, parent_ts, value in cursor:
yield CheckpointTuple(
{"configurable": {"thread_id": thread_id, "thread_ts": thread_ts}},
self.serde.loads(value),
{"configurable": {"thread_id": thread_id, "thread_ts": parent_ts}}
if parent_ts
else None,
(
{
"configurable": {
"thread_id": thread_id,
"thread_ts": parent_ts,
}
}
if parent_ts
else None
),
)
async def aput(
self, config: RunnableConfig, checkpoint: Checkpoint
) -> RunnableConfig:
"""Save a checkpoint to the database asynchronously.
This method saves a checkpoint to the SQLite database. The checkpoint is associated
with the provided config and its parent config (if any).
Args:
config (RunnableConfig): The config to associate with the checkpoint.
checkpoint (Checkpoint): The checkpoint to save.
Returns:
RunnableConfig: The updated config containing the saved checkpoint's timestamp.
"""
await self.setup()
async with self.conn.execute(
"INSERT OR REPLACE INTO checkpoints (thread_id, thread_ts, parent_ts, checkpoint) VALUES (?, ?, ?, ?)",
+85
View File
@@ -14,6 +14,31 @@ from langgraph.checkpoint.base import (
class MemorySaver(BaseCheckpointSaver):
"""An in-memory checkpoint saver.
This checkpoint saver stores checkpoints in memory using a defaultdict.
Args:
serde (Optional[SerializerProtocol]): The serializer to use for serializing and deserializing checkpoints. Defaults to None.
at (Optional[CheckpointAt]): The checkpoint strategy to use. Defaults to None.
Examples:
import asyncio
from langgraph.checkpoint.memory import MemorySaver
from langgraph.graph import StateGraph
builder = StateGraph(int)
builder.add_node("add_one", lambda x: x + 1)
builder.set_entry_point("add_one")
builder.set_finish_point("add_one")
memory = MemorySaver()
graph = builder.compile(checkpointer=memory)
coro = graph.ainvoke(1, {"configurable": {"thread_id": "thread-1"}})
asyncio.run(coro) # Output: 2
"""
storage: defaultdict[str, dict[str, Checkpoint]]
def __init__(
@@ -26,6 +51,19 @@ class MemorySaver(BaseCheckpointSaver):
self.storage = defaultdict(dict)
def get_tuple(self, config: RunnableConfig) -> Optional[CheckpointTuple]:
"""Get a checkpoint tuple from the in-memory storage.
This method retrieves a checkpoint tuple from the in-memory storage based on the
provided config. If the config contains a "thread_ts" key, the checkpoint with
the matching thread ID and timestamp is retrieved. Otherwise, the latest checkpoint
for the given thread ID is retrieved.
Args:
config (RunnableConfig): The config to use for retrieving the checkpoint.
Returns:
Optional[CheckpointTuple]: The retrieved checkpoint tuple, or None if no matching checkpoint was found.
"""
thread_id = config["configurable"]["thread_id"]
if ts := config["configurable"].get("thread_ts"):
if checkpoint := self.storage[thread_id].get(ts):
@@ -47,6 +85,19 @@ class MemorySaver(BaseCheckpointSaver):
before: Optional[RunnableConfig] = None,
limit: Optional[int] = None,
) -> Iterator[CheckpointTuple]:
"""List checkpoints from the in-memory storage.
This method retrieves a list of checkpoint tuples from the in-memory storage based
on the provided config. The checkpoints are ordered by timestamp in descending order.
Args:
config (RunnableConfig): The config to use for listing the checkpoints.
before (Optional[RunnableConfig]): If provided, only checkpoints before the specified timestamp are returned. Defaults to None.
limit (Optional[int]): The maximum number of checkpoints to return. Defaults to None.
Yields:
Iterator[CheckpointTuple]: An iterator of checkpoint tuples.
"""
thread_id = config["configurable"]["thread_id"]
for ts, checkpoint in self.storage[thread_id].items():
if before and ts >= before["configurable"]["thread_ts"]:
@@ -60,6 +111,18 @@ class MemorySaver(BaseCheckpointSaver):
)
def put(self, config: RunnableConfig, checkpoint: Checkpoint) -> RunnableConfig:
"""Save a checkpoint to the in-memory storage.
This method saves a checkpoint to the in-memory storage. The checkpoint is associated
with the provided config.
Args:
config (RunnableConfig): The config to associate with the checkpoint.
checkpoint (Checkpoint): The checkpoint to save.
Returns:
RunnableConfig: The updated config containing the saved checkpoint's timestamp.
"""
self.storage[config["configurable"]["thread_id"]].update(
{checkpoint["ts"]: self.serde.dumps(checkpoint)}
)
@@ -71,11 +134,33 @@ class MemorySaver(BaseCheckpointSaver):
}
async def aget_tuple(self, config: RunnableConfig) -> Optional[CheckpointTuple]:
"""Asynchronous version of get_tuple.
This method is an asynchronous wrapper around get_tuple that runs the synchronous
method in a separate thread using asyncio.
Args:
config (RunnableConfig): The config to use for retrieving the checkpoint.
Returns:
Optional[CheckpointTuple]: The retrieved checkpoint tuple, or None if no matching checkpoint was found.
"""
return await asyncio.get_running_loop().run_in_executor(
None, self.get_tuple, config
)
async def alist(self, config: RunnableConfig) -> AsyncIterator[CheckpointTuple]:
"""Asynchronous version of list.
This method is an asynchronous wrapper around list that runs the synchronous
method in a separate thread using asyncio.
Args:
config (RunnableConfig): The config to use for listing the checkpoints.
Yields:
AsyncIterator[CheckpointTuple]: An asynchronous iterator of checkpoint tuples.
"""
loop = asyncio.get_running_loop()
iter = loop.run_in_executor(None, self.list, config)
while True:
+200 -28
View File
@@ -17,8 +17,30 @@ from langgraph.checkpoint.base import (
from langgraph.serde.jsonplus import JsonPlusSerializer
# for backwards compat we continue to support loading pickled checkpoints
class JsonPlusSerializerCompat(JsonPlusSerializer):
"""A serializer that supports loading pickled checkpoints for backwards compatibility.
This serializer extends the JsonPlusSerializer and adds support for loading pickled
checkpoints. If the input data starts with b"\x80" and ends with b".", it is treated
as a pickled checkpoint and loaded using pickle.loads(). Otherwise, the default
JsonPlusSerializer behavior is used.
Examples:
import pickle
from langgraph.checkpoint.sqlite import JsonPlusSerializerCompat
serializer = JsonPlusSerializerCompat()
pickled_data = pickle.dumps({"key": "value"})
loaded_data = serializer.loads(pickled_data)
print(loaded_data) # Output: {"key": "value"}
json_data = '{"key": "value"}'.encode("utf-8")
loaded_data = serializer.loads(json_data)
print(loaded_data) # Output: {"key": "value"}
"""
def loads(self, data: bytes) -> Any:
if data.startswith(b"\x80") and data.endswith(b"."):
return pickle.loads(data)
@@ -26,10 +48,41 @@ class JsonPlusSerializerCompat(JsonPlusSerializer):
class SqliteSaver(BaseCheckpointSaver, AbstractContextManager):
"""A checkpoint saver that stores checkpoints in a SQLite database.
Note: While useful for demos and small projects, this class does not
scale to multiple threads.
Args:
conn (sqlite3.Connection): The SQLite database connection.
serde (Optional[SerializerProtocol]): The serializer to use for serializing and deserializing checkpoints. Defaults to JsonPlusSerializerCompat.
at (Optional[CheckpointAt]): The checkpoint strategy to use. Defaults to None.
Examples:
import sqlite3
from langgraph.checkpoint.sqlite import SqliteSaver
from langgraph.graph import StateGraph
builder = StateGraph(int)
builder.add_node("add_one", lambda x: x + 1)
builder.set_entry_point("add_one")
builder.set_finish_point("add_one")
conn = sqlite3.connect("checkpoints.sqlite")
memory = SqliteSaver(conn)
graph = builder.compile(checkpointer=memory)
config = {"configurable": {"thread_id": "1"}}
# checkpoint = {"ts": "2023-05-03T10:00:00Z", "data": {"key": "value"}}
result = graph.invoke(3, config)
graph.get_state(config)
# Output: StateSnapshot(values=4, next=(), config={'configurable': {'thread_id': '1', 'thread_ts': '2024-05-04T06:32:42.235444+00:00'}}, parent_config=None)
""" # noqa
serde = JsonPlusSerializerCompat()
conn: sqlite3.Connection
is_setup: bool
def __init__(
@@ -45,6 +98,24 @@ class SqliteSaver(BaseCheckpointSaver, AbstractContextManager):
@classmethod
def from_conn_string(cls, conn_string: str) -> "SqliteSaver":
"""Create a new SqliteSaver instance from a connection string.
Args:
conn_string (str): The SQLite connection string.
Returns:
SqliteSaver: A new SqliteSaver instance.
Examples:
In memory:
memory = SqliteSaver.from_conn_string(":memory:")
To disk:
memory = SqliteSaver.from_conn_string("checkpoints.sqlite")
"""
return SqliteSaver(conn=sqlite3.connect(conn_string))
def __enter__(self) -> Self:
@@ -59,6 +130,12 @@ class SqliteSaver(BaseCheckpointSaver, AbstractContextManager):
return self.conn.close()
def setup(self) -> None:
"""Set up the checkpoint database.
This method creates the necessary tables in the SQLite database if they don't
already exist. It is called automatically when needed and should not be called
directly by the user.
"""
if self.is_setup:
return
@@ -78,6 +155,17 @@ class SqliteSaver(BaseCheckpointSaver, AbstractContextManager):
@contextmanager
def cursor(self, transaction: bool = True):
"""Get a cursor for the SQLite database.
This method returns a cursor for the SQLite database. It is used internally
by the SqliteSaver and should not be called directly by the user.
Args:
transaction (bool): Whether to commit the transaction when the cursor is closed. Defaults to True.
Yields:
sqlite3.Cursor: A cursor for the SQLite database.
"""
self.setup()
cur = self.conn.cursor()
try:
@@ -88,6 +176,38 @@ class SqliteSaver(BaseCheckpointSaver, AbstractContextManager):
cur.close()
def get_tuple(self, config: RunnableConfig) -> Optional[CheckpointTuple]:
"""Get a checkpoint tuple from the database.
This method retrieves a checkpoint tuple from the SQLite database based on the
provided config. If the config contains a "thread_ts" key, the checkpoint with
the matching thread ID and timestamp is retrieved. Otherwise, the latest checkpoint
for the given thread ID is retrieved.
Args:
config (RunnableConfig): The config to use for retrieving the checkpoint.
Returns:
Optional[CheckpointTuple]: The retrieved checkpoint tuple, or None if no matching checkpoint was found.
Examples:
Basic:
config = {"configurable": {"thread_id": "1"}}
checkpoint_tuple = memory.get_tuple(config)
print(checkpoint_tuple) # Output: CheckpointTuple(...)
With timestamp:
config = {
"configurable": {
"thread_id": "1",
"thread_ts": "2024-05-04T06:32:42.235444+00:00",
}
}
checkpoint_tuple = memory.get_tuple(config)
print(checkpoint_tuple) # Output: CheckpointTuple(...)
""" # noqa
with self.cursor(transaction=False) as cur:
if config["configurable"].get("thread_ts"):
cur.execute(
@@ -101,14 +221,16 @@ class SqliteSaver(BaseCheckpointSaver, AbstractContextManager):
return CheckpointTuple(
config,
self.serde.loads(value[0]),
{
"configurable": {
"thread_id": config["configurable"]["thread_id"],
"thread_ts": value[1],
(
{
"configurable": {
"thread_id": config["configurable"]["thread_id"],
"thread_ts": value[1],
}
}
}
if value[1]
else None,
if value[1]
else None
),
)
else:
cur.execute(
@@ -124,14 +246,16 @@ class SqliteSaver(BaseCheckpointSaver, AbstractContextManager):
}
},
self.serde.loads(value[3]),
{
"configurable": {
"thread_id": value[0],
"thread_ts": value[2],
(
{
"configurable": {
"thread_id": value[0],
"thread_ts": value[2],
}
}
}
if value[2]
else None,
if value[2]
else None
),
)
def list(
@@ -141,6 +265,29 @@ class SqliteSaver(BaseCheckpointSaver, AbstractContextManager):
before: Optional[RunnableConfig] = None,
limit: Optional[int] = None,
) -> Iterator[CheckpointTuple]:
"""List checkpoints from the database.
This method retrieves a list of checkpoint tuples from the SQLite database based
on the provided config. The checkpoints are ordered by timestamp in descending order.
Args:
config (RunnableConfig): The config to use for listing the checkpoints.
before (Optional[RunnableConfig]): If provided, only checkpoints before the specified timestamp are returned. Defaults to None.
limit (Optional[int]): The maximum number of checkpoints to return. Defaults to None.
Yields:
Iterator[CheckpointTuple]: An iterator of checkpoint tuples.
Examples:
config = {"configurable": {"thread_id": "1"}}
checkpoints = list(memory.list(config, limit=2))
print(checkpoints) # Output: [CheckpointTuple(...), CheckpointTuple(...)]
config = {"configurable": {"thread_id": "1"}}
before = {"configurable": {"thread_ts": "2024-05-04T06:32:42.235444+00:00"}}
checkpoints = list(memory.list(config, before=before))
print(checkpoints) # Output: [CheckpointTuple(...), ...]
"""
query = (
"SELECT thread_id, thread_ts, parent_ts, checkpoint FROM checkpoints WHERE thread_id = ? ORDER BY thread_ts DESC"
if before is None
@@ -151,28 +298,53 @@ class SqliteSaver(BaseCheckpointSaver, AbstractContextManager):
with self.cursor(transaction=False) as cur:
cur.execute(
query,
(str(config["configurable"]["thread_id"]),)
if before is None
else (
str(config["configurable"]["thread_id"]),
before["configurable"]["thread_ts"],
(
(str(config["configurable"]["thread_id"]),)
if before is None
else (
str(config["configurable"]["thread_id"]),
before["configurable"]["thread_ts"],
)
),
)
for thread_id, thread_ts, parent_ts, value in cur:
yield CheckpointTuple(
{"configurable": {"thread_id": thread_id, "thread_ts": thread_ts}},
self.serde.loads(value),
{
"configurable": {
"thread_id": thread_id,
"thread_ts": parent_ts,
(
{
"configurable": {
"thread_id": thread_id,
"thread_ts": parent_ts,
}
}
}
if parent_ts
else None,
if parent_ts
else None
),
)
def put(self, config: RunnableConfig, checkpoint: Checkpoint) -> RunnableConfig:
"""Save a checkpoint to the database.
This method saves a checkpoint to the SQLite database. The checkpoint is associated
with the provided config and its parent config (if any).
Args:
config (RunnableConfig): The config to associate with the checkpoint.
checkpoint (Checkpoint): The checkpoint to save.
Returns:
RunnableConfig: The updated config containing the saved checkpoint's timestamp.
Examples:
config = {"configurable": {"thread_id": "1"}}
checkpoint = {"ts": "2024-05-04T06:32:42.235444+00:00", "data": {"key": "value"}}
saved_config = memory.put(config, checkpoint)
print(
saved_config
) # Output: {"configurable": {"thread_id": "1", "thread_ts": 2024-05-04T06:32:42.235444+00:00"}}
"""
with self.cursor() as cur:
cur.execute(
"INSERT OR REPLACE INTO checkpoints (thread_id, thread_ts, parent_ts, checkpoint) VALUES (?, ?, ?, ?)",
+92 -3
View File
@@ -14,6 +14,53 @@ Messages = Union[list[MessageLikeRepresentation], MessageLikeRepresentation]
def add_messages(left: Messages, right: Messages) -> Messages:
"""Merges two lists of messages, updating existing messages by ID.
By default, this ensures the state is "append-only", unless the
new message has the same ID as an existing message.
Args:
left: The base list of messages.
right: The list of messages (or single message) to merge
into the base list.
Returns:
A new list of messages with the messages from `right` merged into `left`.
If a message in `right` has the same ID as a message in `left`, the
message from `right` will replace the message from `left`.
Examples:
msgs1 = [HumanMessage(content="Hello", id="1")]
msgs2 = [AIMessage(content="Hi there!", id="2")]
add_messages(msgs1, msgs2)
# [HumanMessage(content="Hello", id="1"), AIMessage(content="Hi there!", id="2")]
msgs1 = [HumanMessage(content="Hello", id="1")]
msgs2 = [HumanMessage(content="Hello again", id="1")]
add_messages(msgs1, msgs2)
# [HumanMessage(content="Hello again", id="1")]
from typing import Annotated
from typing_extensions import TypedDict
from langgraph.graph import StateGraph
class State(TypedDict):
messages: Annotated[list, add_messages]
builder = StateGraph(State)
builder.add_node("chatbot", lambda state: {"messages": [("assistant", "Hello")]})
builder.set_entry_point("chatbot")
builder.set_finish_point("chatbot")
graph = builder.compile()
graph.invoke({})
# {'messages': [AIMessage(content='Hello', id='f657fb65-b6af-4790-a5b5-1d266a2ed26e')]}
"""
# coerce to list
if not isinstance(left, list):
left = [left]
@@ -41,9 +88,51 @@ def add_messages(left: Messages, right: Messages) -> Messages:
class MessageGraph(StateGraph):
"""A StateGraph where every node
- receives a list of messages as input
- returns one or more messages as output."""
"""A StateGraph where every node receives a list of messages as input and returns one or more messages as output.
MessageGraph is a subclass of StateGraph whose entire state is a single, append-only* list of messages.
Each node in a MessageGraph takes a list of messages as input and returns zero or more
messages as output. The `add_messages` function is used to merge the output messages from each node
into the existing list of messages in the graph's state.
Examples:
from langgraph.graph.message import MessageGraph
builder = MessageGraph()
builder.add_node("chatbot", lambda state: [("assistant", "Hello!")])
builder.set_entry_point("chatbot")
builder.set_finish_point("chatbot")
builder.compile().invoke([("user", "Hi there.")])
# {'messages': [HumanMessage(content="Hi there.", id='b8b7d8f4-7f4d-4f4d-9c1d-f8b8d8f4d9c1'),
# AIMessage(content="Hello!", id='f4d9c1d8-8d8f-4d9c-b8b7-d8f4f4d9c1d8')]}
from langchain_core.messages import AIMessage, HumanMessage, ToolMessage
from langgraph.graph.message import MessageGraph
builder = MessageGraph()
builder.add_node(
"chatbot",
lambda state: [
AIMessage(
content="Hello!",
tool_calls=[{"name": "search", "id": "123", "args": {"query": "X"}}],
)
],
)
builder.add_node(
"search", lambda state: [ToolMessage(content="Searching...", tool_call_id="123")]
)
builder.set_entry_point("chatbot")
builder.add_edge("chatbot", "search")
builder.set_finish_point("search")
builder.compile().invoke([HumanMessage(content="Hi there. Can you search for X?")])
# {'messages': [HumanMessage(content="Hi there. Can you search for X?", id='b8b7d8f4-7f4d-4f4d-9c1d-f8b8d8f4d9c1'),
# AIMessage(content="Hello!", id='f4d9c1d8-8d8f-4d9c-b8b7-d8f4f4d9c1d8'),
# ToolMessage(content="Searching...", id='d8f4f4d9-c1d8-4f4d-b8b7-d8f4f4d9c1d8', tool_call_id="123")]}
"""
def __init__(self) -> None:
super().__init__(Annotated[list[AnyMessage], add_messages])
+5
View File
@@ -111,8 +111,13 @@ class StateGraph(Graph):
) -> CompiledGraph:
"""Compiles the state graph into a `CompiledGraph` object.
The compiled graph implements the `Runnable` interface and can be invoked,
streamed, batched, and run asynchronously.
Args:
checkpointer (Optional[BaseCheckpointSaver]): An optional checkpoint saver object.
This serves as a fully versioned "memory" for the graph, allowing
the graph to be paused and resumed, and replayed from any point.
interrupt_before (Optional[Sequence[str]]): An optional list of node names to interrupt before.
interrupt_after (Optional[Sequence[str]]): An optional list of node names to interrupt after.
debug (bool): A flag indicating whether to enable debug mode.
+17 -15
View File
@@ -4,6 +4,7 @@ from typing import Annotated, Sequence, TypedDict, Union
from langchain_core.agents import AgentAction, AgentFinish
from langchain_core.messages import BaseMessage
from langgraph._api.deprecation import deprecated
from langgraph.graph import END, StateGraph
from langgraph.graph.state import CompiledStateGraph
from langgraph.prebuilt.tool_executor import ToolExecutor
@@ -40,6 +41,15 @@ def _get_agent_state(input_schema=None):
return AgentState
@deprecated(
"0.0.44",
alternative="create_tool_calling_executor",
example="""
from langgraph.prebuilt import chat_agent_executor
chat_agent_executor.create_tool_calling_executor(...)
""",
)
def create_agent_executor(
agent_runnable, tools, input_schema=None
) -> CompiledStateGraph:
@@ -53,32 +63,24 @@ def create_agent_executor(
Returns:
The `CompiledStateGraph` object.
Examples:
from langgraph.prebuilt import create_agent_executor
# Since this is deprecated, you should use `create_tool_calling_executor` instead.
# Example usage:
from langgraph.prebuilt import chat_agent_executor
from langchain_openai import ChatOpenAI
from langchain import hub
from langchain.agents import create_openai_functions_agent
from langchain_community.tools.tavily_search import TavilySearchResults
tools = [TavilySearchResults(max_results=1)]
model = ChatOpenAI()
# Get the prompt to use - you can modify this!
prompt = hub.pull("hwchase17/openai-functions-agent")
app = chat_agent_executor.create_tool_calling_executor(model, tools)
# Choose the LLM that will drive the agent
llm = ChatOpenAI(model="gpt-3.5-turbo-1106")
# Construct the OpenAI Functions agent
agent_runnable = create_openai_functions_agent(llm, tools, prompt)
app = create_agent_executor(agent_runnable, tools)
inputs = {"input": "what is the weather in sf", "chat_history": []}
inputs = {"messages": [("user", "what is the weather in sf")]}
for s in app.stream(inputs):
print(list(s.values())[0])
print("----")
"""
if isinstance(tools, ToolExecutor):
+26 -4
View File
@@ -7,6 +7,7 @@ from langchain_core.runnables import Runnable, RunnableLambda
from langchain_core.tools import BaseTool
from langchain_core.utils.function_calling import convert_to_openai_function
from langgraph._api.deprecation import deprecated
from langgraph.checkpoint import BaseCheckpointSaver
from langgraph.graph import END, StateGraph
from langgraph.graph.graph import CompiledGraph
@@ -25,9 +26,30 @@ class AgentState(TypedDict):
messages: Annotated[Sequence[BaseMessage], add_messages]
@deprecated("0.0.44", "create_tool_calling_executor")
def create_function_calling_executor(
model: LanguageModelLike, tools: Union[ToolExecutor, Sequence[BaseTool]]
) -> CompiledGraph:
"""Creates a graph that works with a chat model that utilizes function calling.
Examples:
# Since this is deprecated, you should use `create_tool_calling_executor` instead.
# Example usage:
from langgraph.prebuilt import chat_agent_executor
from langchain_openai import ChatOpenAI
from langchain_community.tools.tavily_search import TavilySearchResults
tools = [TavilySearchResults(max_results=1)]
model = ChatOpenAI()
app = chat_agent_executor.create_tool_calling_executor(model, tools)
inputs = {"messages": [("user", "what is the weather in sf")]}
for s in app.stream(inputs):
print(list(s.values())[0])
print("----")
"""
if isinstance(tools, ToolExecutor):
tool_executor = tools
tool_classes = tools.tools
@@ -165,17 +187,17 @@ def create_tool_calling_executor(
Examples:
from langgraph.prebuilt import chat_agent_executor
from langchain_openai import ChatOpenAI
from langchain_community.tools.tavily_search import TavilySearchResults
from langchain_core.messages import HumanMessage
from langchain_openai import ChatOpenAI
from langgraph.prebuilt import chat_agent_executor
tools = [TavilySearchResults(max_results=1)]
model = ChatOpenAI()
app = chat_agent_executor.create_tool_calling_executor(model, tools)
inputs = {"messages": [HumanMessage(content="what is the weather in sf")]}
inputs = {"messages": [("user", "what is the weather in sf")]}
for s in app.stream(inputs):
print(list(s.values())[0])
print("----")
+53 -4
View File
@@ -13,22 +13,71 @@ INVALID_TOOL_MSG_TEMPLATE = (
class ToolInvocationInterface:
"""Interface for invoking a tool"""
"""Interface for invoking a tool.
Attributes:
tool (str): The name of the tool to invoke.
tool_input (Union[str, dict]): The input to pass to the tool.
"""
tool: str
tool_input: Union[str, dict]
class ToolInvocation(Serializable):
"""Information about how to invoke a tool."""
"""Information about how to invoke a tool.
Attributes:
tool (str): The name of the Tool to execute.
tool_input (Union[str, dict]): The input to pass in to the Tool.
Examples:
invocation = ToolInvocation(
tool="search",
tool_input="What is the capital of France?"
)
"""
tool: str
"""The name of the Tool to execute."""
tool_input: Union[str, dict]
"""The input to pass in to the Tool."""
class ToolExecutor(RunnableCallable):
"""Executes a tool invocation.
Args:
tools (Sequence[BaseTool]): A sequence of tools that can be invoked.
invalid_tool_msg_template (str, optional): The template for the error message
when an invalid tool is requested. Defaults to INVALID_TOOL_MSG_TEMPLATE.
Examples:
from langchain_core.tools import tool
from langgraph.prebuilt.tool_executor import ToolExecutor, ToolInvocation
@tool
def search(query: str) -> str:
\"\"\"Search engine.\"\"\"
return f"Searching for: {query}"
tools = [search]
executor = ToolExecutor(tools)
invocation = ToolInvocation(tool="search", tool_input="What is the capital of France?")
result = executor.invoke(invocation)
print(result) # Output: "Searching for: What is the capital of France?"
invocation = ToolInvocation(
tool="nonexistent", tool_input="What is the capital of France?"
)
result = executor.invoke(invocation)
print(result) # Output: "nonexistent is not a valid tool, try one of [search]."
"""
def __init__(
self,
tools: Sequence[BaseTool],
+28 -28
View File
@@ -113,41 +113,41 @@ def tools_condition(
Examples:
.. code-block:: python
from langchain_anthropic import ChatAnthropic
from langchain_core.tools import tool
from langchain_anthropic import ChatAnthropic
from langchain_core.tools import tool
from langgraph.graph import MessageGraph
from langgraph.prebuilt import ToolNode, tools_condition
from langgraph.graph import MessageGraph
from langgraph.prebuilt import ToolNode, tools_condition
@tool
def divide(a: float, b: float) -> int:
\"\"\"Return a / b.\"\"\"
return a / b
@tool
def divide(a: float, b: float) -> int:
\"\"\"Return a / b.\"\"\"
return a / b
llm = ChatAnthropic(model="claude-3-haiku-20240307")
tools = [divide]
llm = ChatAnthropic(model="claude-3-haiku-20240307")
tools = [divide]
graph_builder = MessageGraph()
graph_builder.add_node("tools", ToolNode(tools))
graph_builder.add_node("chatbot", llm.bind_tools(tools))
graph_builder.add_edge("tools", "chatbot")
graph_builder.add_conditional_edges(
"chatbot",
tools_condition,
{
# If it returns 'action', route to the 'tools' node
"action": "tools",
# If it returns '__end__', route to the end
"__end__": "__end__",
},
)
graph_builder.set_entry_point("chatbot")
graph = graph_builder.compile()
graph.invoke([("user", "What's 329993 divided by 13662?")])
graph_builder = MessageGraph()
graph_builder.add_node("tools", ToolNode(tools))
graph_builder.add_node("chatbot", llm.bind_tools(tools))
graph_builder.add_edge("tools", "chatbot")
graph_builder.add_conditional_edges(
"chatbot",
# highlight-next-line
tools_condition,
{
# If it returns 'action', route to the 'tools' node
"action": "tools",
# If it returns '__end__', route to the end
"__end__": "__end__",
},
)
graph_builder.set_entry_point("chatbot")
graph = graph_builder.compile()
graph.invoke([("user", "What's 329993 divided by 13662?")])
"""
if isinstance(state, list):
ai_message = state[-1]