This commit is contained in:
isaac hershenson
2024-06-18 14:45:06 -07:00
parent 004be5a644
commit ec8fca977e
17 changed files with 202 additions and 125 deletions
@@ -125,7 +125,7 @@
],
"source": [
"# If we list runs on this thread, we can see it is empty\n",
"runs = await client.runs.list(thread['thread_id'])\n",
"runs = await client.runs.list(thread[\"thread_id\"])\n",
"runs"
]
},
@@ -138,7 +138,9 @@
"source": [
"# Let's kick off a run\n",
"input = {\"messages\": [{\"role\": \"human\", \"content\": \"whats the weather in sf\"}]}\n",
"run = await client.runs.create(thread['thread_id'], assistant[\"assistant_id\"], input=input)\n"
"run = await client.runs.create(\n",
" thread[\"thread_id\"], assistant[\"assistant_id\"], input=input\n",
")"
]
},
{
@@ -166,7 +168,7 @@
],
"source": [
"# The first time we poll it, we can see `status=pending`\n",
"await client.runs.get(thread['thread_id'], run['run_id'])"
"await client.runs.get(thread[\"thread_id\"], run[\"run_id\"])"
]
},
{
@@ -449,7 +451,7 @@
],
"source": [
"# We can list events for the run\n",
"await client.runs.list_events(thread['thread_id'], run['run_id'])"
"await client.runs.list_events(thread[\"thread_id\"], run[\"run_id\"])"
]
},
{
@@ -477,7 +479,7 @@
],
"source": [
"# Eventually, it should finish and we should see `status=success`\n",
"await client.runs.get(thread['thread_id'], run['run_id'])"
"await client.runs.get(thread[\"thread_id\"], run[\"run_id\"])"
]
},
{
@@ -488,7 +490,7 @@
"outputs": [],
"source": [
"# We can get the final results\n",
"results = await client.runs.list_events(thread['thread_id'], run['run_id'])"
"results = await client.runs.list_events(thread[\"thread_id\"], run[\"run_id\"])"
]
},
{
@@ -589,7 +591,7 @@
],
"source": [
"# We can get the content of the final message\n",
"final_result['data']['output']['messages'][-1]['content']"
"final_result[\"data\"][\"output\"][\"messages\"][-1][\"content\"]"
]
},
{
+11 -5
View File
@@ -68,7 +68,7 @@
"# We can do this by getting the default assistant\n",
"# There should always be a default assistant with no configuration\n",
"assistants = await client.assistants.search()\n",
"assistants = [a for a in assistants if not a['config']]\n",
"assistants = [a for a in assistants if not a[\"config\"]]\n",
"base_assistant = assistants[0]"
]
},
@@ -93,10 +93,12 @@
],
"source": [
"# We can now call `.get_schemas` to get schemas associated with this graph\n",
"schemas = await client.assistants.get_schemas(assistant_id=base_assistant[\"assistant_id\"])\n",
"schemas = await client.assistants.get_schemas(\n",
" assistant_id=base_assistant[\"assistant_id\"]\n",
")\n",
"# There are multiple types of schemas\n",
"# We can get the `config_schema` to look at the the configurable parameters\n",
"schemas['config_schema']['definitions']['Configurable']['properties']"
"schemas[\"config_schema\"][\"definitions\"][\"Configurable\"][\"properties\"]"
]
},
{
@@ -106,7 +108,9 @@
"metadata": {},
"outputs": [],
"source": [
"assistant = await client.assistants.create(graph_id=\"agent\", config={\"configurable\": {\"model_name\": \"openai\"}})"
"assistant = await client.assistants.create(\n",
" graph_id=\"agent\", config={\"configurable\": {\"model_name\": \"openai\"}}\n",
")"
]
},
{
@@ -163,7 +167,9 @@
"source": [
"thread = await client.threads.create()\n",
"input = {\"messages\": [{\"role\": \"user\", \"content\": \"who made you?\"}]}\n",
"async for event in client.runs.stream(thread['thread_id'], assistant['assistant_id'], input=input):\n",
"async for event in client.runs.stream(\n",
" thread[\"thread_id\"], assistant[\"assistant_id\"], input=input\n",
"):\n",
" print(event)"
]
},
@@ -32,9 +32,9 @@
"metadata": {},
"outputs": [],
"source": [
"from langgraph_sdk import get_client\n",
"import httpx\n",
"from langchain_core.messages import convert_to_messages\n",
"import httpx"
"from langgraph_sdk import get_client"
]
},
{
@@ -77,7 +77,7 @@
"run = await client.runs.create(\n",
" thread[\"thread_id\"],\n",
" assistant[\"assistant_id\"],\n",
" input={\"messages\": [{\"role\": \"human\", \"content\": \"whats the weather in sf?\"}]}\n",
" input={\"messages\": [{\"role\": \"human\", \"content\": \"whats the weather in sf?\"}]},\n",
")"
]
},
@@ -222,7 +222,8 @@
"source": [
"# the first run will be interrupted\n",
"interrupted_run = await client.runs.create(\n",
" thread[\"thread_id\"], assistant[\"assistant_id\"],\n",
" thread[\"thread_id\"],\n",
" assistant[\"assistant_id\"],\n",
" input={\"messages\": [{\"role\": \"human\", \"content\": \"whats the weather in sf?\"}]},\n",
")\n",
"await asyncio.sleep(2)\n",
@@ -377,7 +378,8 @@
"source": [
"# the first run will be interrupted\n",
"rolled_back_run = await client.runs.create(\n",
" thread[\"thread_id\"], assistant[\"assistant_id\"],\n",
" thread[\"thread_id\"],\n",
" assistant[\"assistant_id\"],\n",
" input={\"messages\": [{\"role\": \"human\", \"content\": \"whats the weather in sf?\"}]},\n",
")\n",
"await asyncio.sleep(2)\n",
@@ -479,7 +481,7 @@
"source": [
"try:\n",
" await client.runs.get(thread[\"thread_id\"], rolled_back_run[\"run_id\"])\n",
"except httpx.HTTPStatusError as e:\n",
"except httpx.HTTPStatusError as _:\n",
" print(\"Original run was correctly deleted\")"
]
},
@@ -512,7 +514,7 @@
"first_run = await client.runs.create(\n",
" thread[\"thread_id\"],\n",
" assistant[\"assistant_id\"],\n",
" input={\"messages\": [{\"role\": \"human\", \"content\": \"whats the weather in sf?\"}]}\n",
" input={\"messages\": [{\"role\": \"human\", \"content\": \"whats the weather in sf?\"}]},\n",
")"
]
},
@@ -60,7 +60,7 @@
"source": [
"# There should always be a default assistant with no configuration\n",
"assistants = await client.assistants.search()\n",
"assistants = [a for a in assistants if not a['config']]\n",
"assistants = [a for a in assistants if not a[\"config\"]]\n",
"assistants"
]
},
@@ -142,7 +142,7 @@
}
],
"source": [
"runs = await client.runs.list(thread['thread_id'])\n",
"runs = await client.runs.list(thread[\"thread_id\"])\n",
"runs"
]
},
@@ -188,7 +188,11 @@
"source": [
"input = {\"messages\": [{\"role\": \"human\", \"content\": \"whats the weather in sf\"}]}\n",
"async for chunk in client.runs.stream(\n",
" thread['thread_id'], assistant['assistant_id'], input=input, stream_mode=\"updates\", interrupt_before=['action']\n",
" thread[\"thread_id\"],\n",
" assistant[\"assistant_id\"],\n",
" input=input,\n",
" stream_mode=\"updates\",\n",
" interrupt_before=[\"action\"],\n",
"):\n",
" print(f\"Receiving new event of type: {chunk.event}...\")\n",
" print(chunk.data)\n",
@@ -239,7 +243,11 @@
"source": [
"input = None\n",
"async for chunk in client.runs.stream(\n",
" thread['thread_id'], assistant['assistant_id'], input=input, stream_mode=\"updates\", interrupt_before=['action']\n",
" thread[\"thread_id\"],\n",
" assistant[\"assistant_id\"],\n",
" input=input,\n",
" stream_mode=\"updates\",\n",
" interrupt_before=[\"action\"],\n",
"):\n",
" print(f\"Receiving new event of type: {chunk.event}...\")\n",
" print(chunk.data)\n",
@@ -289,7 +297,11 @@
"source": [
"input = {\"messages\": [{\"role\": \"human\", \"content\": \"whats the weather in la?\"}]}\n",
"async for chunk in client.runs.stream(\n",
" thread['thread_id'], assistant['assistant_id'], input=input, stream_mode=\"updates\", interrupt_before=['action']\n",
" thread[\"thread_id\"],\n",
" assistant[\"assistant_id\"],\n",
" input=input,\n",
" stream_mode=\"updates\",\n",
" interrupt_before=[\"action\"],\n",
"):\n",
" print(f\"Receiving new event of type: {chunk.event}...\")\n",
" print(chunk.data)\n",
@@ -311,7 +323,7 @@
"metadata": {},
"outputs": [],
"source": [
"thread_state = await client.threads.get_state(thread['thread_id'])"
"thread_state = await client.threads.get_state(thread[\"thread_id\"])"
]
},
{
@@ -329,7 +341,7 @@
"metadata": {},
"outputs": [],
"source": [
"last_message = thread_state['values']['messages'][-1]"
"last_message = thread_state[\"values\"][\"messages\"][-1]"
]
},
{
@@ -353,7 +365,7 @@
}
],
"source": [
"last_message['content']"
"last_message[\"content\"]"
]
},
{
@@ -371,12 +383,14 @@
"metadata": {},
"outputs": [],
"source": [
"last_message['tool_calls'] = [{\n",
" 'id': last_message['tool_calls'][0]['id'],\n",
" 'name': 'tavily_search_results_json',\n",
" # We change the query to say temperature\n",
" 'args': {'query': 'weather in Louisiana'}\n",
"}]\n",
"last_message[\"tool_calls\"] = [\n",
" {\n",
" \"id\": last_message[\"tool_calls\"][0][\"id\"],\n",
" \"name\": \"tavily_search_results_json\",\n",
" # We change the query to say temperature\n",
" \"args\": {\"query\": \"weather in Louisiana\"},\n",
" }\n",
"]\n",
"# last_message['content'] = [{\n",
"# 'id': last_message['content'][0]['id'],\n",
"# 'name': 'tavily_search_results_json',\n",
@@ -413,7 +427,9 @@
}
],
"source": [
"await client.threads.update_state(thread['thread_id'], values={\"messages\": [last_message]})"
"await client.threads.update_state(\n",
" thread[\"thread_id\"], values={\"messages\": [last_message]}\n",
")"
]
},
{
@@ -444,8 +460,8 @@
}
],
"source": [
"thread_state = await client.threads.get_state(thread['thread_id'])\n",
"thread_state['values']['messages'][-1]['tool_calls']"
"thread_state = await client.threads.get_state(thread[\"thread_id\"])\n",
"thread_state[\"values\"][\"messages\"][-1][\"tool_calls\"]"
]
},
{
@@ -492,7 +508,11 @@
"source": [
"input = None\n",
"async for chunk in client.runs.stream(\n",
" thread['thread_id'], assistant['assistant_id'], input=input, stream_mode=\"updates\", interrupt_before=['action']\n",
" thread[\"thread_id\"],\n",
" assistant[\"assistant_id\"],\n",
" input=input,\n",
" stream_mode=\"updates\",\n",
" interrupt_before=[\"action\"],\n",
"):\n",
" print(f\"Receiving new event of type: {chunk.event}...\")\n",
" print(chunk.data)\n",
@@ -517,7 +537,7 @@
"metadata": {},
"outputs": [],
"source": [
"thread_history = await client.threads.get_history(thread['thread_id'], limit=100)"
"thread_history = await client.threads.get_history(thread[\"thread_id\"], limit=100)"
]
},
{
@@ -571,7 +591,7 @@
],
"source": [
"rewind_state = thread_history[3]\n",
"rewind_state['values']['messages'][-1]['tool_calls']"
"rewind_state[\"values\"][\"messages\"][-1][\"tool_calls\"]"
]
},
{
@@ -593,7 +613,7 @@
}
],
"source": [
"rewind_state['config']"
"rewind_state[\"config\"]"
]
},
{
@@ -640,12 +660,12 @@
"source": [
"input = None\n",
"async for chunk in client.runs.stream(\n",
" thread['thread_id'], \n",
" assistant['assistant_id'], \n",
" input=input, \n",
" stream_mode=\"updates\", \n",
" interrupt_before=['action'],\n",
" config=rewind_state['config']\n",
" thread[\"thread_id\"],\n",
" assistant[\"assistant_id\"],\n",
" input=input,\n",
" stream_mode=\"updates\",\n",
" interrupt_before=[\"action\"],\n",
" config=rewind_state[\"config\"],\n",
"):\n",
" print(f\"Receiving new event of type: {chunk.event}...\")\n",
" print(chunk.data)\n",
+16 -4
View File
@@ -23,11 +23,13 @@
"\n",
"client = get_client()\n",
"\n",
"openai_assistant = await client.assistants.create(graph_id=\"agent\", config={\"configurable\": {\"model_name\": \"openai\"}})\n",
"openai_assistant = await client.assistants.create(\n",
" graph_id=\"agent\", config={\"configurable\": {\"model_name\": \"openai\"}}\n",
")\n",
"\n",
"# There should always be a default assistant with no configuration\n",
"assistants = await client.assistants.search()\n",
"default_assistant = [a for a in assistants if not a['config']][0]"
"default_assistant = [a for a in assistants if not a[\"config\"]][0]"
]
},
{
@@ -117,7 +119,12 @@
"source": [
"thread = await client.threads.create()\n",
"input = {\"messages\": [{\"role\": \"user\", \"content\": \"who made you?\"}]}\n",
"async for event in client.runs.stream(thread['thread_id'], openai_assistant['assistant_id'], input=input, stream_mode='updates'):\n",
"async for event in client.runs.stream(\n",
" thread[\"thread_id\"],\n",
" openai_assistant[\"assistant_id\"],\n",
" input=input,\n",
" stream_mode=\"updates\",\n",
"):\n",
" print(event)"
]
},
@@ -147,7 +154,12 @@
],
"source": [
"input = {\"messages\": [{\"role\": \"user\", \"content\": \"and you?\"}]}\n",
"async for event in client.runs.stream(thread['thread_id'], default_assistant['assistant_id'], input=input, stream_mode='updates'):\n",
"async for event in client.runs.stream(\n",
" thread[\"thread_id\"],\n",
" default_assistant[\"assistant_id\"],\n",
" input=input,\n",
" stream_mode=\"updates\",\n",
"):\n",
" print(event)"
]
},
+22 -16
View File
@@ -63,7 +63,9 @@
"metadata": {},
"outputs": [],
"source": [
"assistant = await client.assistants.create(graph_id=\"agent\", config={\"configurable\": {\"model_name\": \"openai\"}})"
"assistant = await client.assistants.create(\n",
" graph_id=\"agent\", config={\"configurable\": {\"model_name\": \"openai\"}}\n",
")"
]
},
{
@@ -135,7 +137,7 @@
}
],
"source": [
"runs = await client.runs.list(thread['thread_id'])\n",
"runs = await client.runs.list(thread[\"thread_id\"])\n",
"runs"
]
},
@@ -148,11 +150,14 @@
"source": [
"# Helper function for formatting messages\n",
"\n",
"\n",
"def format_tool_calls(tool_calls):\n",
" if tool_calls:\n",
" formatted_calls = []\n",
" for call in tool_calls:\n",
" formatted_calls.append(f\"Tool Call ID: {call['id']}, Function: {call['name']}, Arguments: {call['args']}\")\n",
" formatted_calls.append(\n",
" f\"Tool Call ID: {call['id']}, Function: {call['name']}, Arguments: {call['args']}\"\n",
" )\n",
" return \"\\n\".join(formatted_calls)\n",
" return \"No tool calls\""
]
@@ -1346,35 +1351,36 @@
"source": [
"input = {\"messages\": [{\"role\": \"user\", \"content\": \"whats the weather in sf\"}]}\n",
"\n",
"async for event in client.runs.stream(thread['thread_id'], assistant['assistant_id'], input=input, stream_mode='messages'):\n",
" if event.event == 'metadata':\n",
"async for event in client.runs.stream(\n",
" thread[\"thread_id\"], assistant[\"assistant_id\"], input=input, stream_mode=\"messages\"\n",
"):\n",
" if event.event == \"metadata\":\n",
" print(f\"Metadata: Run ID - {event.data['run_id']}\")\n",
" elif event.event == 'data':\n",
" elif event.event == \"data\":\n",
" for data_item in event.data:\n",
" if 'role' in data_item and data_item['role'] == 'user':\n",
" if \"role\" in data_item and data_item[\"role\"] == \"user\":\n",
" print(f\"Human: {data_item['content']}\")\n",
" else:\n",
" tool_calls = data_item.get('tool_calls', [])\n",
" invalid_tool_calls = data_item.get('invalid_tool_calls', [])\n",
" content = data_item.get('content', \"\")\n",
" response_metadata = data_item.get('response_metadata', {})\n",
" tool_calls = data_item.get(\"tool_calls\", [])\n",
" invalid_tool_calls = data_item.get(\"invalid_tool_calls\", [])\n",
" content = data_item.get(\"content\", \"\")\n",
" response_metadata = data_item.get(\"response_metadata\", {})\n",
"\n",
" if content:\n",
" print(f\"AI: {content}\")\n",
" \n",
"\n",
" if tool_calls:\n",
" print(\"Tool Calls:\")\n",
" print(format_tool_calls(tool_calls))\n",
" \n",
"\n",
" if invalid_tool_calls:\n",
" print(\"Invalid Tool Calls:\")\n",
" print(format_tool_calls(invalid_tool_calls))\n",
"\n",
" if response_metadata:\n",
" finish_reason = response_metadata.get('finish_reason', 'N/A')\n",
" finish_reason = response_metadata.get(\"finish_reason\", \"N/A\")\n",
" print(f\"Response Metadata: Finish Reason - {finish_reason}\")\n",
" print(\"-\" * 50)\n",
" "
" print(\"-\" * 50)"
]
}
],
@@ -62,7 +62,7 @@
"source": [
"# There should always be a default assistant with no configuration\n",
"assistants = await client.assistants.search()\n",
"assistants = [a for a in assistants if not a['config']]\n",
"assistants = [a for a in assistants if not a[\"config\"]]\n",
"assistants"
]
},
@@ -136,7 +136,7 @@
}
],
"source": [
"runs = await client.runs.list(thread['thread_id'])\n",
"runs = await client.runs.list(thread[\"thread_id\"])\n",
"runs"
]
},
@@ -180,7 +180,12 @@
],
"source": [
"input = {\"messages\": [{\"role\": \"human\", \"content\": \"whats the weather in la\"}]}\n",
"async for chunk in client.runs.stream(thread['thread_id'], assistant['assistant_id'], input=input, stream_mode=\"updates\", ):\n",
"async for chunk in client.runs.stream(\n",
" thread[\"thread_id\"],\n",
" assistant[\"assistant_id\"],\n",
" input=input,\n",
" stream_mode=\"updates\",\n",
"):\n",
" print(f\"Receiving new event of type: {chunk.event}...\")\n",
" print(chunk.data)\n",
" print(\"\\n\\n\")"
+7 -3
View File
@@ -62,7 +62,7 @@
"source": [
"# There should always be a default assistant with no configuration\n",
"assistants = await client.assistants.search()\n",
"assistants = [a for a in assistants if not a['config']]\n",
"assistants = [a for a in assistants if not a[\"config\"]]\n",
"assistants"
]
},
@@ -139,7 +139,9 @@
"source": [
"input = {\"messages\": [{\"role\": \"human\", \"content\": \"whats the weather in la\"}]}\n",
"thread = await client.threads.create()\n",
"async for chunk in client.runs.stream(thread['thread_id'], assistant['assistant_id'], input=input):\n",
"async for chunk in client.runs.stream(\n",
" thread[\"thread_id\"], assistant[\"assistant_id\"], input=input\n",
"):\n",
" print(f\"Receiving new event of type: {chunk.event}...\")\n",
" print(chunk.data)\n",
" print(\"\\n\\n\")"
@@ -163,7 +165,9 @@
"input = {\"messages\": [{\"role\": \"human\", \"content\": \"whats the weather in la\"}]}\n",
"thread = await client.threads.create()\n",
"final_answer = None\n",
"async for chunk in client.runs.stream(thread['thread_id'], assistant['assistant_id'], input=input):\n",
"async for chunk in client.runs.stream(\n",
" thread[\"thread_id\"], assistant[\"assistant_id\"], input=input\n",
"):\n",
" if chunk.event == \"values\":\n",
" final_answer = chunk.data"
]
+3 -3
View File
@@ -475,9 +475,9 @@
"metadata": {},
"outputs": [],
"source": [
"current_values.values[\"messages\"][-1].tool_calls[0][\"args\"][\n",
" \"query\"\n",
"] = \"weather in San Francisco, Accuweather\""
"current_values.values[\"messages\"][-1].tool_calls[0][\"args\"][\"query\"] = (\n",
" \"weather in San Francisco, Accuweather\"\n",
")"
]
},
{
+3 -3
View File
@@ -729,9 +729,9 @@
"metadata": {},
"outputs": [],
"source": [
"current_values.values[\"messages\"][-1].tool_calls[0][\"args\"][\n",
" \"query\"\n",
"] = \"weather in San Francisco today\""
"current_values.values[\"messages\"][-1].tool_calls[0][\"args\"][\"query\"] = (\n",
" \"weather in San Francisco today\"\n",
")"
]
},
{
+54 -26
View File
@@ -43,11 +43,11 @@
"execution_count": 1,
"id": "6c05a600f1afb5b6",
"metadata": {
"collapsed": false,
"ExecuteTime": {
"end_time": "2024-06-12T21:24:00.532147Z",
"start_time": "2024-06-12T21:24:00.526043Z"
}
},
"collapsed": false
},
"outputs": [],
"source": [
@@ -78,11 +78,11 @@
"execution_count": 2,
"id": "64b0bf1b14c2e902",
"metadata": {
"collapsed": false,
"ExecuteTime": {
"end_time": "2024-06-12T21:24:09.918436Z",
"start_time": "2024-06-12T21:24:09.608563Z"
}
},
"collapsed": false
},
"outputs": [
{
@@ -125,11 +125,11 @@
"execution_count": 3,
"id": "a60191bd3489f278",
"metadata": {
"collapsed": false,
"ExecuteTime": {
"end_time": "2024-06-12T21:24:14.663745Z",
"start_time": "2024-06-12T21:24:13.527958Z"
}
},
"collapsed": false
},
"outputs": [],
"source": [
@@ -142,11 +142,11 @@
"execution_count": 4,
"id": "1f1e1f4f86ed54",
"metadata": {
"collapsed": false,
"ExecuteTime": {
"end_time": "2024-06-12T21:24:15.891582Z",
"start_time": "2024-06-12T21:24:15.289782Z"
}
},
"collapsed": false
},
"outputs": [
{
@@ -192,11 +192,11 @@
"execution_count": 5,
"id": "deae8460e4cf72b1",
"metadata": {
"collapsed": false,
"ExecuteTime": {
"end_time": "2024-06-12T21:24:17.557848Z",
"start_time": "2024-06-12T21:24:17.508550Z"
}
},
"collapsed": false
},
"outputs": [],
"source": [
@@ -214,6 +214,7 @@
" [RunnableLambda(handle_tool_error)], exception_key=\"error\"\n",
" )\n",
"\n",
"\n",
"def handle_tool_error(state) -> dict:\n",
" error = state.get(\"error\")\n",
" tool_calls = state[\"messages\"][-1].tool_calls\n",
@@ -341,6 +342,7 @@
" return \"Error: Query failed. Please rewrite your query and try again.\"\n",
" return result\n",
"\n",
"\n",
"print(db_query_tool.invoke(\"SELECT * FROM Artist LIMIT 10;\"))"
]
},
@@ -393,8 +395,12 @@
"\n",
"You will call the appropriate tool to execute the query after running this check.\"\"\"\n",
"\n",
"query_check_prompt = ChatPromptTemplate.from_messages([(\"system\", query_check_system),(\"placeholder\", \"{messages}\")])\n",
"query_check = query_check_prompt | ChatOpenAI(model=\"gpt-4o\", temperature=0).bind_tools([db_query_tool], tool_choice=\"required\")\n",
"query_check_prompt = ChatPromptTemplate.from_messages(\n",
" [(\"system\", query_check_system), (\"placeholder\", \"{messages}\")]\n",
")\n",
"query_check = query_check_prompt | ChatOpenAI(model=\"gpt-4o\", temperature=0).bind_tools(\n",
" [db_query_tool], tool_choice=\"required\"\n",
")\n",
"\n",
"query_check.invoke({\"messages\": [(\"user\", \"SELET * FROM Artist LIMIT 10;\")]})"
]
@@ -440,9 +446,11 @@
"class State(TypedDict):\n",
" messages: Annotated[list[AnyMessage], add_messages]\n",
"\n",
"\n",
"# Define a new graph\n",
"workflow = StateGraph(State)\n",
"\n",
"\n",
"# Add a node for the first tool call\n",
"def first_tool_call(state: State) -> dict[str, list[AIMessage]]:\n",
" return {\n",
@@ -452,8 +460,7 @@
" tool_calls=[\n",
" {\n",
" \"name\": \"sql_db_list_tables\",\n",
" \"args\": {\n",
" },\n",
" \"args\": {},\n",
" \"id\": \"tool_abcd123\",\n",
" }\n",
" ],\n",
@@ -461,31 +468,41 @@
" ]\n",
" }\n",
"\n",
"\n",
"def model_check_query(state: State) -> dict[str, list[AIMessage]]:\n",
" \"\"\"\n",
" Use this tool to double-check if your query is correct before executing it.\n",
" \"\"\"\n",
" return {\n",
" \"messages\": [\n",
" query_check.invoke({\"messages\": [state[\"messages\"][-1]]})\n",
" ]\n",
" }\n",
" return {\"messages\": [query_check.invoke({\"messages\": [state[\"messages\"][-1]]})]}\n",
"\n",
"\n",
"workflow.add_node(\"first_tool_call\", first_tool_call)\n",
"\n",
"# Add nodes for the first two tools\n",
"workflow.add_node(\"list_tables_tool\", create_tool_node_with_fallback([list_tables_tool]))\n",
"workflow.add_node(\n",
" \"list_tables_tool\", create_tool_node_with_fallback([list_tables_tool])\n",
")\n",
"workflow.add_node(\"get_schema_tool\", create_tool_node_with_fallback([get_schema_tool]))\n",
"\n",
"# Add a node for a model to choose the relevant tables based on the question and available tables\n",
"model_get_schema = ChatOpenAI(model=\"gpt-4o\", temperature=0).bind_tools([get_schema_tool])\n",
"workflow.add_node(\"model_get_schema\", lambda state: {\"messages\": [model_get_schema.invoke(state[\"messages\"])],})\n",
"model_get_schema = ChatOpenAI(model=\"gpt-4o\", temperature=0).bind_tools(\n",
" [get_schema_tool]\n",
")\n",
"workflow.add_node(\n",
" \"model_get_schema\",\n",
" lambda state: {\n",
" \"messages\": [model_get_schema.invoke(state[\"messages\"])],\n",
" },\n",
")\n",
"\n",
"\n",
"# Describe a tool to represent the end state\n",
"class SubmitFinalAnswer(BaseModel):\n",
" \"\"\"Submit the final answer to the user based on the query results.\"\"\"\n",
"\n",
" final_answer: str = Field(..., description=\"The final answer to the user\")\n",
"\n",
"\n",
"# Add a node for a model to generate a query based on the question and schema\n",
"query_gen_system = \"\"\"You are a SQL expert with a strong attention to detail.\n",
"\n",
@@ -509,11 +526,17 @@
"If you have enough information to answer the input question, simply invoke the appropriate tool to submit the final answer to the user.\n",
"\n",
"DO NOT make any DML statements (INSERT, UPDATE, DELETE, DROP etc.) to the database.\"\"\"\n",
"query_gen_prompt = ChatPromptTemplate.from_messages([(\"system\", query_gen_system),(\"placeholder\", \"{messages}\")])\n",
"query_gen = query_gen_prompt | ChatOpenAI(model=\"gpt-4o\", temperature=0).bind_tools([SubmitFinalAnswer])\n",
"query_gen_prompt = ChatPromptTemplate.from_messages(\n",
" [(\"system\", query_gen_system), (\"placeholder\", \"{messages}\")]\n",
")\n",
"query_gen = query_gen_prompt | ChatOpenAI(model=\"gpt-4o\", temperature=0).bind_tools(\n",
" [SubmitFinalAnswer]\n",
")\n",
"\n",
"\n",
"def query_gen_node(state: State):\n",
" message = query_gen.invoke(state)\n",
" \n",
"\n",
" # Sometimes, the LLM will hallucinate and call the wrong tool. We need to catch this and return an error message.\n",
" tool_messages = []\n",
" if message.tool_calls:\n",
@@ -529,6 +552,7 @@
" tool_messages = []\n",
" return {\"messages\": [message] + tool_messages}\n",
"\n",
"\n",
"workflow.add_node(\"query_gen\", query_gen_node)\n",
"\n",
"# Add a node for the model to check the query before executing it\n",
@@ -537,6 +561,7 @@
"# Add node for executing the query\n",
"workflow.add_node(\"execute_query\", create_tool_node_with_fallback([db_query_tool]))\n",
"\n",
"\n",
"# Define a conditional edge to decide whether to continue or end the workflow\n",
"def should_continue(state: State) -> Literal[END, \"correct_query\", \"query_gen\"]:\n",
" messages = state[\"messages\"]\n",
@@ -549,6 +574,7 @@
" else:\n",
" return \"correct_query\"\n",
"\n",
"\n",
"# Specify the edges between the nodes\n",
"workflow.set_entry_point(\"first_tool_call\")\n",
"workflow.add_edge(\"first_tool_call\", \"list_tables_tool\")\n",
@@ -649,7 +675,9 @@
}
],
"source": [
"for event in app.stream({\"messages\": [(\"user\", \"Which sales agent made the most in sales in 2009?\")]}):\n",
"for event in app.stream(\n",
" {\"messages\": [(\"user\", \"Which sales agent made the most in sales in 2009?\")]}\n",
"):\n",
" print(event)"
]
}
+2 -4
View File
@@ -125,12 +125,10 @@ class Graph:
return self.edges
@overload
def add_node(self, node: RunnableLike) -> None:
...
def add_node(self, node: RunnableLike) -> None: ...
@overload
def add_node(self, node: str, action: RunnableLike) -> None:
...
def add_node(self, node: str, action: RunnableLike) -> None: ...
def add_node(
self, node: Union[str, RunnableLike], action: Optional[RunnableLike] = None
+1 -2
View File
@@ -63,8 +63,7 @@ class ManagedValue(ABC, Generic[V]):
pass
@abstractmethod
def __call__(self, step: int, task: PregelTaskDescription) -> V:
...
def __call__(self, step: int, task: PregelTaskDescription) -> V: ...
class ConfiguredManagedValue(NamedTuple):
+1
View File
@@ -1,4 +1,5 @@
"""langgraph.prebuilt exposes a higher-level API for creating and executing agents and tools."""
from langgraph.prebuilt import chat_agent_executor
from langgraph.prebuilt.agent_executor import create_agent_executor
from langgraph.prebuilt.chat_agent_executor import create_react_agent
+4 -8
View File
@@ -126,8 +126,7 @@ class Channel:
*,
key: Optional[str] = None,
tags: Optional[list[str]] = None,
) -> PregelNode:
...
) -> PregelNode: ...
@overload
@classmethod
@@ -137,8 +136,7 @@ class Channel:
*,
key: None = None,
tags: Optional[list[str]] = None,
) -> PregelNode:
...
) -> PregelNode: ...
@classmethod
def subscribe_to(
@@ -1609,8 +1607,7 @@ def _prepare_next_tasks(
step: int,
for_execution: Literal[False],
manager: Literal[None] = None,
) -> tuple[Checkpoint, list[PregelTaskDescription]]:
...
) -> tuple[Checkpoint, list[PregelTaskDescription]]: ...
@overload
@@ -1623,8 +1620,7 @@ def _prepare_next_tasks(
step: int,
for_execution: Literal[True],
manager: Union[ParentRunManager, AsyncParentRunManager],
) -> tuple[Checkpoint, list[PregelExecutableTask]]:
...
) -> tuple[Checkpoint, list[PregelExecutableTask]]: ...
def _prepare_next_tasks(
+3 -3
View File
@@ -51,9 +51,9 @@ class RetryPolicy(NamedTuple):
"""Maximum number of attempts to make before giving up, including the first."""
jitter: bool = True
"""Whether to add random jitter to the interval between retries."""
retry_on: Union[
tuple[Exception, ...], Callable[[Exception], bool]
] = default_retry_on
retry_on: Union[tuple[Exception, ...], Callable[[Exception], bool]] = (
default_retry_on
)
"""List of exceptions that should trigger a retry, or a callable that returns True for exceptions that should trigger a retry."""
+2 -4
View File
@@ -10,8 +10,6 @@ class SerializerProtocol(Protocol):
Valid implementations include the `pickle`, `json` and `orjson` modules.
"""
def dumps(self, obj: Any) -> bytes:
...
def dumps(self, obj: Any) -> bytes: ...
def loads(self, data: bytes) -> Any:
...
def loads(self, data: bytes) -> Any: ...