mirror of
https://github.com/langchain-ai/langgraph.git
synced 2026-08-25 09:02:25 +02:00
Deprecate Chat agent executor & Function Calling Executor in Docs (#392)
This commit is contained in:
@@ -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
|
||||
@@ -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
|
||||
@@ -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`
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
+158
-209
File diff suppressed because one or more lines are too long
@@ -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
|
||||
@@ -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 (?, ?, ?, ?)",
|
||||
|
||||
@@ -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
@@ -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 (?, ?, ?, ?)",
|
||||
|
||||
@@ -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])
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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("----")
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -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]
|
||||
|
||||
Reference in New Issue
Block a user