diff --git a/examples/agent_executor.ipynb b/examples/agent_executor.ipynb new file mode 100644 index 000000000..610a74998 --- /dev/null +++ b/examples/agent_executor.ipynb @@ -0,0 +1,104 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "e2fdcac4-d134-402b-b423-b0cf4b939f5d", + "metadata": {}, + "outputs": [], + "source": [ + "from langgraph.prebuilt.agent_executor import create_agent_executor\n", + "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": 2, + "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": "code", + "execution_count": 4, + "id": "9be722f0-c9ab-4bd2-af27-66adf51134d2", + "metadata": {}, + "outputs": [], + "source": [ + "app = create_agent_executor(agent_runnable, tools)" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "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': 'weather in San Francisco'}, log=\"\\nInvoking: `tavily_search_results_json` with `{'query': 'weather in San Francisco'}`\\n\\n\\n\", message_log=[AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\"query\":\"weather in San Francisco\"}', 'name': 'tavily_search_results_json'}})])}\n", + "----\n", + "{'intermediate_steps': [(AgentActionMessageLog(tool='tavily_search_results_json', tool_input={'query': 'weather in San Francisco'}, log=\"\\nInvoking: `tavily_search_results_json` with `{'query': 'weather in San Francisco'}`\\n\\n\\n\", message_log=[AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\"query\":\"weather in San Francisco\"}', 'name': 'tavily_search_results_json'}})]), \"[{'url': 'https://weatherspark.com/h/m/557/2024/1/Historical-Weather-in-January-2024-in-San-Francisco-California-United-States', 'content': 'January 2024 Weather History in San Francisco California, United States Daily Precipitation in January 2024 in San Francisco Observed Weather in January 2024 in San Francisco San Francisco Temperature History January 2024 Hourly Temperature in January 2024 in San Francisco Hours of Daylight and Twilight in January 2024 in San FranciscoThis report shows the past weather for San Francisco, providing a weather history for January 2024. It features all historical weather data series we have available, including the San Francisco temperature history for January 2024. You can drill down from year to month and even day level reports by clicking on the graphs.'}]\")]}\n", + "----\n", + "{'agent_outcome': AgentFinish(return_values={'output': \"It seems that I couldn't retrieve the current weather information for San Francisco. I recommend checking a reliable weather website or using a weather app to get the most up-to-date weather forecast for San Francisco.\"}, log=\"It seems that I couldn't retrieve the current weather information for San Francisco. I recommend checking a reliable weather website or using a weather app to get the most up-to-date weather forecast for San Francisco.\")}\n", + "----\n", + "{'input': 'what is the weather in sf', 'agent_outcome': AgentFinish(return_values={'output': \"It seems that I couldn't retrieve the current weather information for San Francisco. I recommend checking a reliable weather website or using a weather app to get the most up-to-date weather forecast for San Francisco.\"}, log=\"It seems that I couldn't retrieve the current weather information for San Francisco. I recommend checking a reliable weather website or using a weather app to get the most up-to-date weather forecast for San Francisco.\"), 'intermediate_steps': [(AgentActionMessageLog(tool='tavily_search_results_json', tool_input={'query': 'weather in San Francisco'}, log=\"\\nInvoking: `tavily_search_results_json` with `{'query': 'weather in San Francisco'}`\\n\\n\\n\", message_log=[AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\"query\":\"weather in San Francisco\"}', 'name': 'tavily_search_results_json'}})]), \"[{'url': 'https://weatherspark.com/h/m/557/2024/1/Historical-Weather-in-January-2024-in-San-Francisco-California-United-States', 'content': 'January 2024 Weather History in San Francisco California, United States Daily Precipitation in January 2024 in San Francisco Observed Weather in January 2024 in San Francisco San Francisco Temperature History January 2024 Hourly Temperature in January 2024 in San Francisco Hours of Daylight and Twilight in January 2024 in San FranciscoThis report shows the past weather for San Francisco, providing a weather history for January 2024. It features all historical weather data series we have available, including the San Francisco temperature history for January 2024. You can drill down from year to month and even day level reports by clicking on the graphs.'}]\")]}\n", + "----\n" + ] + } + ], + "source": [ + "inputs = {\"input\": \"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": "162f647a-36b4-45c3-a171-c8452b05af01", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.1" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/langgraph.ipynb b/examples/langgraph.ipynb index 34fd6c3f4..cc558d276 100644 --- a/examples/langgraph.ipynb +++ b/examples/langgraph.ipynb @@ -1,400 +1,1241 @@ { - "cells": [ - { - "cell_type": "markdown", - "id": "396e20d9-8684-40ea-a46a-e3dfa36ed5a6", - "metadata": {}, - "source": [ - "## Existing Agent Executor" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "d642e6af-217a-4414-a78c-509b44155eca", - "metadata": {}, - "outputs": [], - "source": [ - "from langchain import hub\n", - "from langchain.agents import create_openai_functions_agent\n", - "from langchain.chat_models import ChatOpenAI\n", - "from langchain_community.chat_models import ChatOpenAI\n", - "from langchain_community.tools.tavily_search import TavilySearchResults\n", - "from langchain_core.runnables import RunnablePassthrough\n", - "\n", - "from langgraph.graph import END, Graph\n", - "\n", - "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)\n", - "\n", - "from langchain_core.agents import AgentFinish\n", - "# Define decision-making logic\n", - "def should_continue(data):\n", - " # Logic to decide whether to continue in the loop or exit\n", - " if isinstance(data['agent_outcome'], AgentFinish):\n", - " return \"exit\"\n", - " else:\n", - " return \"continue\"\n", - " \n", - "def execute_tools(data):\n", - " agent_action = data.pop('agent_outcome')\n", - " observation = {t.name: t for t in tools}[agent_action.tool].invoke(agent_action.tool_input)\n", - " data['intermediate_steps'].append((agent_action, observation))\n", - " return data\n", - " \n", - " \n", - "\n", - "# Define agents\n", - "agent = RunnablePassthrough.assign(\n", - " agent_outcome = agent_runnable\n", - ")\n", - "\n", - "\n", - "# Define a new graph\n", - "workflow = Graph()\n", - "\n", - "workflow.add_node(\"agent\", agent)\n", - "workflow.add_node(\"tools\", execute_tools)\n", - "\n", - "workflow.set_entry_point(\"agent\")\n", - "\n", - "workflow.add_conditional_edges(\n", - " \"agent\",\n", - " should_continue,\n", - " {\n", - " \"continue\": \"tools\",\n", - " \"exit\": END\n", - " }\n", - ")\n", - "\n", - "workflow.add_edge('tools', 'agent')\n", - "\n", - "chain = workflow.compile()" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "c46bd262-9605-4449-9391-f6b6e0fe440e", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'input': 'what is the weather in sf',\n", - " 'intermediate_steps': [(AgentActionMessageLog(tool='tavily_search_results_json', tool_input={'query': 'weather in San Francisco'}, log=\"\\nInvoking: `tavily_search_results_json` with `{'query': 'weather in San Francisco'}`\\n\\n\\n\", message_log=[AIMessage(content='', additional_kwargs={'function_call': {'name': 'tavily_search_results_json', 'arguments': '{\"query\":\"weather in San Francisco\"}'}})]),\n", - " [{'url': 'https://www.weather25.com/north-america/usa/california/san-francisco',\n", - " 'content': 'will give you an idea of weather trends in San Francisco. For example, the weather in San Francisco in January 2024. San Francisco 14 day weather The weather today in San Francisco San Francisco weather report The weather in San Francisco, United States San Francisco weather by months San Francisco weather the weather in San Francisco including humidity, wind, chance of rain and more on the San Francisco current weather01 January 02 February 03 March 04 April 05 May 06 June 07 July 08 August 09 September 10 October 11 November 12 December. ... For example, the weather in San Francisco in January 2024. These trends can be helpful when planning trips to San Francisco or preparing for the weather in advance. There are many factors to consider when looking at the ...'}])],\n", - " 'agent_outcome': AgentFinish(return_values={'output': 'For the current weather in San Francisco, you can visit the following website: [San Francisco Weather](https://www.weather25.com/north-america/usa/california/san-francisco). This will provide you with the latest weather updates including humidity, wind, chance of rain, and more.'}, log='For the current weather in San Francisco, you can visit the following website: [San Francisco Weather](https://www.weather25.com/north-america/usa/california/san-francisco). This will provide you with the latest weather updates including humidity, wind, chance of rain, and more.')}" - ] - }, - "execution_count": 2, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "chain.invoke({\"input\": \"what is the weather in sf\", \"intermediate_steps\": []})" - ] - }, - { - "cell_type": "markdown", - "id": "592c3886-71d1-4539-80dd-111e55cc3a85", - "metadata": {}, - "source": [ - "## Reflexion Agent" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "f6f96e81-4a20-4599-a625-8d18df6fa76d", - "metadata": {}, - "outputs": [], - "source": [ - "from langchain.agents import AgentExecutor, BaseMultiActionAgent, Tool\n", - "from langchain.schema import AgentAction, AgentFinish\n", - "from langchain_core.language_models.chat_models import BaseChatModel\n", - "from langchain.chains import LLMChain\n", - "\n", - "from langchain.globals import set_llm_cache\n", - "\n", - "from dotenv import load_dotenv\n", - "\n", - "from pydantic import BaseModel\n", - "\n", - "from langchain.chat_models import ChatOpenAI\n", - "from langchain.cache import SQLiteCache\n", - "\n", - "from langchain_core.output_parsers import BaseOutputParser\n", - "\n", - "from langchain.prompts.chat import ChatPromptTemplate\n", - "from langchain.callbacks import get_openai_callback\n", - "from langchain.tools.tavily_search import TavilySearchResults\n", - "from langchain.utilities.tavily_search import TavilySearchAPIWrapper\n", - "from langchain.pydantic_v1 import BaseModel\n", - "import os\n", - "\n", - "from langchain.agents import AgentType, initialize_agent, load_tools\n", - "\n", - "set_llm_cache(SQLiteCache(database_path=\".langchain.db\"))\n", - "\n", - "\n", - "llm = ChatOpenAI(\n", - " temperature=0.0,\n", - " max_tokens=2000,\n", - " max_retries=100,\n", - " model=\"gpt-4-1106-preview\",\n", - ")\n", - "\n", - "search = TavilySearchAPIWrapper()\n", - "tavily_tool = TavilySearchResults(api_wrapper=search, max_results=5)\n", - "\n", - "NEXT_STEP_TEMPLATE = \"\"\"You are expert researcher trying answer a question ~250 words. You are asked to answer the following question: {question}\n", - "\n", - "The way you are going to answer the question is as follows:\n", - "\n", - "1. Revise your previous answer using the new information.\n", - " - You should use the previous critique to add important information to your answer.\n", - " _ You MUST include numerical citations in your revised answer to ensure it can be verified.\n", - " - Add a \"References\" section to the bottom of your answer (which does not count towards the word limit). In form of:\n", - " - [1] https://example.com\n", - " - [2] https://example.com\n", - " - You should use the previous critique to remove superfluous information from your answer and make SURE it is not more than 250 words.\n", - "2. Reflect and critique your answer. Specifically, you should:\n", - " - Think about what is missing from your answer.\n", - " - Think about what is superfluous in your answer.\n", - " - Think about what search query you should use next to improve your answer.\n", - " Give your answer in exactly 2 parts. The first should address what is missing from your answer. The second should address what could be removed from your answer. Your should be VERY harsh as we really want to improve the answer.\n", - "3. Give the search query you came up with to improve your answer.\n", - "\n", - "Previous steps: \n", - "\n", - "{previous_steps}\n", - "\n", - "===\n", - "\n", - "Format your answer as follows:\n", - "\n", - "Revised answer: [give your revised answer based on the previous critique and new information from the search engine then the \"References\" section]\n", - "Critique: [give your harsh critique of your revised answer in 2 parts: what is missing and what is superfluous]\n", - "Search query: [give the new search query you came up with to enter into the search engine to improve your answer. If you have more than one, make sure they are comma separated and in quotes]\n", - "\n", - "SAY NOTHING else please.\"\"\"\n", - "\n", - "INITIAL_ANSWER_TEMPLATE = \"\"\"You are expert researcher trying answer a question ~250 words. You are asked to answer the following question: {question}\n", - "\n", - "The way you are going to answer the question is as follows:\n", - "\n", - "1. Give a detailed in ~250 words.\n", - "2. Reflect and critique your answer. Specifically, you should:\n", - " - Think about what is missing from your answer.\n", - " - Think about what is superfluous in your answer.\n", - " - Think about what search query you should use next to improve your answer.\n", - " Give your answer in exactly 2 parts. The first should address what is missing from your answer. The second should address what could be removed from your answer. Your should be VERY harsh as we really want to improve the answer.\n", - "3. Give the search query you came up with to improve your answer.\n", - "\n", - "===\n", - "\n", - "Format your answer as follows:\n", - "\n", - "Answer: [give your initial answer]\n", - "Critique: [give your harsh critique of your answer in 2 parts: what is missing and what is superfluous]\n", - "Search query: [give the search query you came up with to improve your answer. If you have more than one, make sure they are comma separated and in quotes]\n", - "\n", - "SAY NOTHING else please.\"\"\"\n", - "\n", - "\n", - "class ReflexionStep(BaseModel):\n", - " \"\"\"A single step in the reflexion process.\"\"\"\n", - "\n", - " answer: str\n", - " critique: str\n", - " search_query: str\n", - "\n", - " def __str__(self):\n", - " return f\"Answer: {self.answer}\\nCritique: {self.critique}\\nSearch query: {self.search_query}\"\n", - "\n", - "def _parse_reflexion_step(output: str) -> tuple[str, str, str]:\n", - " # find answer using .split()\n", - " if (\"Answer:\" not in output and \"Revised answer:\" not in output) or not \"Critique:\" in output or not \"Search query:\" in output:\n", - " raise ValueError(f\"The output is not formatted correctly. Output: {output}\")\n", - " if \"Answer:\" in output:\n", - " answer = output.split(\"Answer:\")[1].split(\"Critique:\")[0].strip()\n", - " else:\n", - " answer = output.split(\"Revised answer:\")[1].split(\"Critique:\")[0].strip()\n", - " critique = output.split(\"Critique:\")[1].split(\"Search query:\")[0].strip()\n", - " search_query = output.split(\"Search query:\")[1].strip()\n", - " return answer, critique, search_query\n", - "\n", - "class ReflexionStepParser(BaseOutputParser[ReflexionStep]):\n", - " \"\"\"Parser for the reflexion step.\"\"\"\n", - "\n", - " def parse(self, output: str) -> ReflexionStep:\n", - " \"\"\"Parse the output.\"\"\"\n", - " # try to find answer or initial answer\n", - " answer, critique, search_query = _parse_reflexion_step(output)\n", - " return ReflexionStep(\n", - " answer=answer, critique=critique, search_query=search_query\n", - " )" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "7708fa95-547b-4bea-b126-3656de7d5873", - "metadata": {}, - "outputs": [], - "source": [ - "initial_chain = RunnablePassthrough.assign(\n", - " agent_outcome = ChatPromptTemplate.from_template(INITIAL_ANSWER_TEMPLATE) | llm | ReflexionStepParser() | (lambda x: AgentAction(\n", - " tool=\"tavily_search_results_json\",\n", - " tool_input=x.search_query,\n", - " log=str(x),\n", - " ))\n", - ")\n", - "\n", - "def prep_next(inputs):\n", - " intermediate_steps = inputs[\"intermediate_steps\"]\n", - " previous_steps = list[str]()\n", - "\n", - " for i, (action, observation) in enumerate(intermediate_steps, start=1):\n", - " last_step_str = f\"\"\"Step {i}:\n", - "\n", - "{action.log}\n", - "\n", - "Search output for \"{action.tool_input}\":\n", - "\n", - "{observation}\"\"\"\n", - " previous_steps.append(last_step_str)\n", - "\n", - " previous_steps_str = \"\\n\\n\".join(previous_steps)\n", - " inputs[\"previous_steps\"] = previous_steps_str\n", - " return inputs\n", - " \n", - "next_chain = RunnablePassthrough.assign(\n", - " agent_outcome = prep_next | ChatPromptTemplate.from_template(NEXT_STEP_TEMPLATE) | llm | ReflexionStepParser() | (lambda x: AgentAction(\n", - " tool=\"tavily_search_results_json\",\n", - " tool_input=x.search_query,\n", - " log=str(x),\n", - " ))\n", - ")\n", - "\n", - "def finish(inputs):\n", - " intermediate_steps = inputs[\"intermediate_steps\"]\n", - " last_action, _ = intermediate_steps[-1]\n", - " last_step_str = last_action.log\n", - " # extract answer\n", - " answer, _, _ = _parse_reflexion_step(last_step_str)\n", - "\n", - " first_action, _ = intermediate_steps[0]\n", - " first_step_str = first_action.log\n", - " # extract answer\n", - " initial_answer, _, _ = _parse_reflexion_step(first_step_str)\n", - "\n", - " return AgentFinish(\n", - " log=\"Reached max steps.\",\n", - " return_values={\"output\": answer, \"initial_answer\": initial_answer},\n", - " )\n", - "\n", - "\n", - "def execute_tools(data):\n", - " agent_action = data.pop('agent_outcome')\n", - " observation = {t.name: t for t in tools}[agent_action.tool].invoke(agent_action.tool_input)\n", - " data['intermediate_steps'].append((agent_action, observation))\n", - " return data\n" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "d6cdd1cd-e480-4dd7-99b4-9018eb243b4d", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "AgentFinish(return_values={'output': \"The current weather in San Francisco can be accessed through various weather reporting services, which provide real-time temperature, humidity, wind, and chances of precipitation [1]. Historically, San Francisco experiences a mild, Mediterranean climate with average temperatures ranging from the low 50s to the mid-60s Fahrenheit. The city's unique topography creates microclimates, leading to significant weather variations across different neighborhoods. San Francisco's summers are notably cooler compared to other Californian cities, largely due to the cold California Current and persistent fog, especially in June and July. Winters are mild and the wettest months span from November to March, with an annual rainfall average of approximately 23 inches. Wind is a prominent feature, with spring being particularly windy. For historical weather extremes and average wind speeds, additional specific data can be sought from climatological records.\\n\\nReferences:\\n[1] https://www.weather25.com/north-america/usa/california/san-francisco\", 'initial_answer': \"The weather in San Francisco (SF) is characterized by a mild, Mediterranean-like climate with wet winters and dry summers. The city's unique topography and coastal location result in microclimates, where weather conditions can vary significantly from one neighborhood to another. Average temperatures typically range from the low 50s to the mid-60s Fahrenheit throughout the year. Summers in San Francisco are often cooler than in other parts of California due to the cold California Current offshore and the presence of fog, particularly in June and July. The fog usually burns off by the afternoon, leading to clearer skies and slightly warmer temperatures. Winters are mild and moist, with the majority of the city's rainfall occurring between November and March. Rainfall averages around 23 inches annually. Wind is also a notable feature of San Francisco's weather, with spring being the windiest season. Despite the general patterns, it's always advisable to dress in layers due to the potential for rapid weather changes.\"}, log='Reached max steps.')" - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "workflow = Graph()\n", - "\n", - "# add actors\n", - "workflow.add_node(\"initial\", initial_chain)\n", - "workflow.add_node(\"next\", next_chain)\n", - "workflow.add_node(\"finish\", finish)\n", - "workflow.add_node(\"tools\", execute_tools)\n", - "\n", - "# Enter with initial actor, then loop through tools -> next steps until finished\n", - "workflow.set_entry_point('initial')\n", - "\n", - "workflow.add_edge('initial', 'tools')\n", - "workflow.add_conditional_edges(\n", - " 'tools',\n", - " lambda x: \"exit\" if len(x['intermediate_steps']) >= 2 else \"continue\",\n", - " {\n", - " \"continue\": 'next',\n", - " \"exit\": 'finish'\n", - " }\n", - ")\n", - "workflow.add_edge('next', 'tools')\n", - "workflow.set_finish_point('finish')\n", - "\n", - "chain = workflow.compile()\n", - "\n", - "chain.invoke({\"question\": \"what is the weather in sf\", \"intermediate_steps\": []})" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "9babf196-b1fd-492d-9197-96a674f5e81d", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "58ce0d58-fb00-4dc1-a12b-8fc015474611", - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.11.5" - } + "cells": [ + { + "cell_type": "markdown", + "id": "396e20d9-8684-40ea-a46a-e3dfa36ed5a6", + "metadata": {}, + "source": [ + "## Existing Agent Executor" + ] }, - "nbformat": 4, - "nbformat_minor": 5 + { + "cell_type": "code", + "execution_count": 1, + "id": "d642e6af-217a-4414-a78c-509b44155eca", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/harrisonchase/workplace/langchain/libs/core/langchain_core/_api/deprecation.py:117: LangChainDeprecationWarning: The class `langchain_community.chat_models.openai.ChatOpenAI` was deprecated in langchain-community 0.0.10 and will be removed in 0.2.0. An updated version of the class exists in the langchain-openai package and should be used instead. To use it run `pip install -U langchain-openai` and import as `from langchain_openai import ChatOpenAI`.\n", + " warn_deprecated(\n" + ] + } + ], + "source": [ + "from langchain import hub\n", + "from langchain.agents import create_openai_functions_agent\n", + "from langchain.chat_models import ChatOpenAI\n", + "from langchain_community.chat_models import ChatOpenAI\n", + "from langchain_community.tools.tavily_search import TavilySearchResults\n", + "from langchain_core.runnables import RunnablePassthrough\n", + "from langgraph.prebuilt.tool_executor import ToolExecutor\n", + "from langgraph.prebuilt.agent_executor import create_agent_executor\n", + "\n", + "from langgraph.graph import END, Graph\n", + "\n", + "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)\n", + "tool_executor = ToolExecutor(tools)\n", + "chain = create_agent_executor(agent_runnable, tool_executor)" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "c46bd262-9605-4449-9391-f6b6e0fe440e", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'input': 'what is the weather in sf',\n", + " 'intermediate_steps': [(AgentActionMessageLog(tool='tavily_search_results_json', tool_input={'query': 'weather in San Francisco'}, log=\"\\nInvoking: `tavily_search_results_json` with `{'query': 'weather in San Francisco'}`\\n\\n\\n\", message_log=[AIMessage(content='', additional_kwargs={'function_call': {'name': 'tavily_search_results_json', 'arguments': '{\"query\":\"weather in San Francisco\"}'}})]),\n", + " [{'url': 'https://www.whereandwhen.net/when/north-america/california/san-francisco-ca/january/',\n", + " 'content': 'Best time to go to San Francisco? Weather in San Francisco in january 2024 How was the weather last january? Here is the day by day recorded weather in San Francisco in january 2023: Seasonal average climate and temperature of San Francisco in january 8% 46% 29% 12% 8% Evolution of daily average temperature and precipitation in San Francisco in januaryWeather in San Francisco in january 2024. The weather in San Francisco in january comes from statistical datas on the past years. You can view the weather statistics the entire month, but also by using the tabs for the beginning, the middle and the end of the month. ... 12-01-2023 50°F to 59°F. 13-01-2023 54°F to 58°F. 14-01-2023 54°F to ...'}])],\n", + " 'agent_outcome': AgentFinish(return_values={'output': 'I found information about the weather in San Francisco in January 2024. It looks like the weather statistics for January 2023 are available, showing the average temperatures and precipitation for each day. If you need current weather information, I can help you find a reliable source for that.'}, log='I found information about the weather in San Francisco in January 2024. It looks like the weather statistics for January 2023 are available, showing the average temperatures and precipitation for each day. If you need current weather information, I can help you find a reliable source for that.')}" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "chain.invoke({\"input\": \"what is the weather in sf\"})" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "dff32d55-f8b3-45fd-8de7-daa973aaa20b", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "2d518968-2c61-4f4b-a2ae-8f8a545a0e7b", + "metadata": {}, + "source": [ + "## Agent Messages Executor" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "4c468b87-3ff4-4d61-87bb-4fe0b61bca13", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_core.agents import AgentAction\n", + "from langchain_core.messages import FunctionMessage, HumanMessage, SystemMessage\n", + "from langchain.tools.render import format_tool_to_openai_function\n", + "import json\n", + "from langchain.chat_models import ChatOpenAI\n", + "\n", + "from langchain_community.tools.tavily_search import TavilySearchResults\n", + "\n", + "from langgraph.prebuilt.tool_executor import ToolExecutor\n", + "from langgraph.prebuilt.agent_messages_executor import create_agent_messages_executor\n", + "tools = [TavilySearchResults(max_results=1)]\n", + "model = ChatOpenAI().bind_functions([format_tool_to_openai_function(t) for t in tools])" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "659351eb-194c-4cc0-8f8b-ebbcc753ee38", + "metadata": {}, + "outputs": [], + "source": [ + "def call_model(messages):\n", + " response = model.invoke(messages)\n", + " return messages + [response]\n", + "\n", + "\n", + "def exit(messages):\n", + " last_message = messages[-1]\n", + " if \"function_call\" not in last_message.additional_kwargs:\n", + " return \"end\"\n", + " else:\n", + " return \"function\"\n", + "\n", + "tool_executor = ToolExecutor(tools)\n", + "def call_tool(messages):\n", + " last_message = messages[-1]\n", + " action = AgentAction(\n", + " tool=last_message.additional_kwargs[\"function_call\"][\"name\"],\n", + " tool_input=json.loads(last_message.additional_kwargs[\"function_call\"][\"arguments\"]),\n", + " log=\"\",\n", + " )\n", + " response = tool_executor.execute(action)\n", + " function_message = FunctionMessage(content=str(response[0][1]), name=action.tool)\n", + " return messages + [function_message]" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "b78d8ba1-a428-4022-b5fc-cfb0744d2ac1", + "metadata": {}, + "outputs": [], + "source": [ + "chain = create_agent_messages_executor(call_model, call_tool, exit)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "e6f7d494-d922-4f9b-8499-36b179917522", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[HumanMessage(content='what is the weather in sf'), AIMessage(content='', additional_kwargs={'function_call': {'name': 'tavily_search_results_json', 'arguments': '{\\n \"query\": \"weather in San Francisco\"\\n}'}})]\n", + "----\n", + "[HumanMessage(content='what is the weather in sf'), AIMessage(content='', additional_kwargs={'function_call': {'name': 'tavily_search_results_json', 'arguments': '{\\n \"query\": \"weather in San Francisco\"\\n}'}}), FunctionMessage(content=\"[{'url': 'https://www.whereandwhen.net/when/north-america/california/san-francisco-ca/january/', 'content': 'Best time to go to San Francisco? Weather in San Francisco in january 2024 How was the weather last january? Here is the day by day recorded weather in San Francisco in january 2023: Seasonal average climate and temperature of San Francisco in january 8% 46% 29% 12% 8% Evolution of daily average temperature and precipitation in San Francisco in januaryWeather in San Francisco in january 2024. The weather in San Francisco in january comes from statistical datas on the past years. You can view the weather statistics the entire month, but also by using the tabs for the beginning, the middle and the end of the month. ... 12-01-2023 50°F to 59°F. 13-01-2023 54°F to 58°F. 14-01-2023 54°F to ...'}]\", name='tavily_search_results_json')]\n", + "----\n", + "[HumanMessage(content='what is the weather in sf'), AIMessage(content='', additional_kwargs={'function_call': {'name': 'tavily_search_results_json', 'arguments': '{\\n \"query\": \"weather in San Francisco\"\\n}'}}), FunctionMessage(content=\"[{'url': 'https://www.whereandwhen.net/when/north-america/california/san-francisco-ca/january/', 'content': 'Best time to go to San Francisco? Weather in San Francisco in january 2024 How was the weather last january? Here is the day by day recorded weather in San Francisco in january 2023: Seasonal average climate and temperature of San Francisco in january 8% 46% 29% 12% 8% Evolution of daily average temperature and precipitation in San Francisco in januaryWeather in San Francisco in january 2024. The weather in San Francisco in january comes from statistical datas on the past years. You can view the weather statistics the entire month, but also by using the tabs for the beginning, the middle and the end of the month. ... 12-01-2023 50°F to 59°F. 13-01-2023 54°F to 58°F. 14-01-2023 54°F to ...'}]\", name='tavily_search_results_json'), AIMessage(content=\"I apologize, but I couldn't find the current weather information for San Francisco. However, you can check the weather in San Francisco for January 2024 on this website: [San Francisco Weather in January](https://www.whereandwhen.net/when/north-america/california/san-francisco-ca/january/).\")]\n", + "----\n", + "[HumanMessage(content='what is the weather in sf'), AIMessage(content='', additional_kwargs={'function_call': {'name': 'tavily_search_results_json', 'arguments': '{\\n \"query\": \"weather in San Francisco\"\\n}'}}), FunctionMessage(content=\"[{'url': 'https://www.whereandwhen.net/when/north-america/california/san-francisco-ca/january/', 'content': 'Best time to go to San Francisco? Weather in San Francisco in january 2024 How was the weather last january? Here is the day by day recorded weather in San Francisco in january 2023: Seasonal average climate and temperature of San Francisco in january 8% 46% 29% 12% 8% Evolution of daily average temperature and precipitation in San Francisco in januaryWeather in San Francisco in january 2024. The weather in San Francisco in january comes from statistical datas on the past years. You can view the weather statistics the entire month, but also by using the tabs for the beginning, the middle and the end of the month. ... 12-01-2023 50°F to 59°F. 13-01-2023 54°F to 58°F. 14-01-2023 54°F to ...'}]\", name='tavily_search_results_json'), AIMessage(content=\"I apologize, but I couldn't find the current weather information for San Francisco. However, you can check the weather in San Francisco for January 2024 on this website: [San Francisco Weather in January](https://www.whereandwhen.net/when/north-america/california/san-francisco-ca/january/).\")]\n", + "----\n" + ] + } + ], + "source": [ + "messages = [HumanMessage(content=\"what is the weather in sf\")]\n", + "for s in chain.stream(messages):\n", + " print(list(s.values())[0])\n", + " print(\"----\")" + ] + }, + { + "cell_type": "markdown", + "id": "479f258a-6b61-42a5-85bb-c1c141f6d1fb", + "metadata": {}, + "source": [ + "### Human in the Loop\n", + "\n", + "#### Require confirmation" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "b7e94bc5-38c0-403c-8902-91eafd7e0c92", + "metadata": {}, + "outputs": [], + "source": [ + "def call_tool(messages):\n", + " last_message = messages[-1]\n", + " action = AgentAction(\n", + " tool=last_message.additional_kwargs[\"function_call\"][\"name\"],\n", + " tool_input=json.loads(last_message.additional_kwargs[\"function_call\"][\"arguments\"]),\n", + " log=\"\",\n", + " )\n", + " response = input(prompt=f\"[y/n] Okay to call this tool? {action}\")\n", + " if response == \"n\":\n", + " raise ValueError\n", + " response = tool_executor.execute(action)\n", + " function_message = FunctionMessage(content=str(response[0][1]), name=action.tool)\n", + " return messages + [function_message]" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "e112af6b-6339-4a54-bf90-dcc0a3b3f7ed", + "metadata": {}, + "outputs": [], + "source": [ + "chain = create_agent_messages_executor(call_model, call_tool, exit)" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "b3ad0780-8f1f-4ddd-9472-6f6400f0dcee", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[HumanMessage(content='what is the weather in sf'), AIMessage(content='', additional_kwargs={'function_call': {'name': 'tavily_search_results_json', 'arguments': '{\\n \"query\": \"weather in San Francisco\"\\n}'}})]\n", + "----\n" + ] + }, + { + "name": "stdin", + "output_type": "stream", + "text": [ + "[y/n] Okay to call this tool? tool='tavily_search_results_json' tool_input={'query': 'weather in San Francisco'} log='' \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[HumanMessage(content='what is the weather in sf'), AIMessage(content='', additional_kwargs={'function_call': {'name': 'tavily_search_results_json', 'arguments': '{\\n \"query\": \"weather in San Francisco\"\\n}'}}), FunctionMessage(content=\"[{'url': 'https://www.whereandwhen.net/when/north-america/california/san-francisco-ca/january/', 'content': 'Best time to go to San Francisco? Weather in San Francisco in january 2024 How was the weather last january? Here is the day by day recorded weather in San Francisco in january 2023: Seasonal average climate and temperature of San Francisco in january 8% 46% 29% 12% 8% Evolution of daily average temperature and precipitation in San Francisco in januaryWeather in San Francisco in january 2024. The weather in San Francisco in january comes from statistical datas on the past years. You can view the weather statistics the entire month, but also by using the tabs for the beginning, the middle and the end of the month. ... 12-01-2023 50°F to 59°F. 13-01-2023 54°F to 58°F. 14-01-2023 54°F to ...'}]\", name='tavily_search_results_json')]\n", + "----\n", + "[HumanMessage(content='what is the weather in sf'), AIMessage(content='', additional_kwargs={'function_call': {'name': 'tavily_search_results_json', 'arguments': '{\\n \"query\": \"weather in San Francisco\"\\n}'}}), FunctionMessage(content=\"[{'url': 'https://www.whereandwhen.net/when/north-america/california/san-francisco-ca/january/', 'content': 'Best time to go to San Francisco? Weather in San Francisco in january 2024 How was the weather last january? Here is the day by day recorded weather in San Francisco in january 2023: Seasonal average climate and temperature of San Francisco in january 8% 46% 29% 12% 8% Evolution of daily average temperature and precipitation in San Francisco in januaryWeather in San Francisco in january 2024. The weather in San Francisco in january comes from statistical datas on the past years. You can view the weather statistics the entire month, but also by using the tabs for the beginning, the middle and the end of the month. ... 12-01-2023 50°F to 59°F. 13-01-2023 54°F to 58°F. 14-01-2023 54°F to ...'}]\", name='tavily_search_results_json'), AIMessage(content=\"I'm sorry, but I couldn't retrieve the current weather information for San Francisco. However, you can visit this [link](https://www.whereandwhen.net/when/north-america/california/san-francisco-ca/january/) to check the weather statistics for San Francisco in January.\")]\n", + "----\n", + "[HumanMessage(content='what is the weather in sf'), AIMessage(content='', additional_kwargs={'function_call': {'name': 'tavily_search_results_json', 'arguments': '{\\n \"query\": \"weather in San Francisco\"\\n}'}}), FunctionMessage(content=\"[{'url': 'https://www.whereandwhen.net/when/north-america/california/san-francisco-ca/january/', 'content': 'Best time to go to San Francisco? Weather in San Francisco in january 2024 How was the weather last january? Here is the day by day recorded weather in San Francisco in january 2023: Seasonal average climate and temperature of San Francisco in january 8% 46% 29% 12% 8% Evolution of daily average temperature and precipitation in San Francisco in januaryWeather in San Francisco in january 2024. The weather in San Francisco in january comes from statistical datas on the past years. You can view the weather statistics the entire month, but also by using the tabs for the beginning, the middle and the end of the month. ... 12-01-2023 50°F to 59°F. 13-01-2023 54°F to 58°F. 14-01-2023 54°F to ...'}]\", name='tavily_search_results_json'), AIMessage(content=\"I'm sorry, but I couldn't retrieve the current weather information for San Francisco. However, you can visit this [link](https://www.whereandwhen.net/when/north-america/california/san-francisco-ca/january/) to check the weather statistics for San Francisco in January.\")]\n", + "----\n" + ] + } + ], + "source": [ + "messages = [HumanMessage(content=\"what is the weather in sf\")]\n", + "for s in chain.stream(messages):\n", + " print(list(s.values())[0])\n", + " print(\"----\")" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "1653c386-dd60-4283-ac03-21f00d57bddb", + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "def call_tool(messages):\n", + " last_message = messages[-1]\n", + " action = AgentAction(\n", + " tool=last_message.additional_kwargs[\"function_call\"][\"name\"],\n", + " tool_input=json.loads(last_message.additional_kwargs[\"function_call\"][\"arguments\"]),\n", + " log=\"\",\n", + " )\n", + " response = input(prompt=f\"[y/n] Okay to call this tool? {action}\")\n", + " if response == \"**EXIT**\":\n", + " raise ValueError\n", + " elif response:\n", + " print(\"foo\")\n", + " action.tool_input = response\n", + " response = tool_executor.execute(action)\n", + " function_message = FunctionMessage(content=str(response[0][1]), name=action.tool)\n", + " return messages + [function_message]" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "b3d06fbc-a8d8-4a2a-b436-98975a69ef38", + "metadata": {}, + "outputs": [], + "source": [ + "chain = create_agent_messages_executor(call_model, call_tool, exit)" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "1aeb7d89-2cb1-4e41-98b4-cb645d1245c6", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[HumanMessage(content='what is the weather in sf'), AIMessage(content='', additional_kwargs={'function_call': {'name': 'tavily_search_results_json', 'arguments': '{\\n \"query\": \"weather in San Francisco\"\\n}'}})]\n", + "----\n" + ] + }, + { + "name": "stdin", + "output_type": "stream", + "text": [ + "[y/n] Okay to call this tool? tool='tavily_search_results_json' tool_input={'query': 'weather in San Francisco'} log='' {'query': 'current weather in SF'}\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "foo\n", + "[HumanMessage(content='what is the weather in sf'), AIMessage(content='', additional_kwargs={'function_call': {'name': 'tavily_search_results_json', 'arguments': '{\\n \"query\": \"weather in San Francisco\"\\n}'}}), FunctionMessage(content=\"[{'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 FranciscoData: 1991 - 2021 Min. Temperature °C (°F), Max. Temperature °C (°F), Precipitation / Rainfall mm (in), Humidity, Rainy days. Data: 1999 - 2019: avg. Sun hours San Francisco weather and climate for further months San Francisco in February San Francisco in March San Francisco in April San Francisco in May San Francisco in June San Francisco in July'}]\", name='tavily_search_results_json')]\n", + "----\n", + "[HumanMessage(content='what is the weather in sf'), AIMessage(content='', additional_kwargs={'function_call': {'name': 'tavily_search_results_json', 'arguments': '{\\n \"query\": \"weather in San Francisco\"\\n}'}}), FunctionMessage(content=\"[{'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 FranciscoData: 1991 - 2021 Min. Temperature °C (°F), Max. Temperature °C (°F), Precipitation / Rainfall mm (in), Humidity, Rainy days. Data: 1999 - 2019: avg. Sun hours San Francisco weather and climate for further months San Francisco in February San Francisco in March San Francisco in April San Francisco in May San Francisco in June San Francisco in July'}]\", name='tavily_search_results_json'), AIMessage(content=\"I'm sorry, but I couldn't find the current weather in San Francisco. However, you can visit [this website](https://en.climate-data.org/north-america/united-states-of-america/california/san-francisco-385/t/january-1/) to find information about the weather in San Francisco for the month of January.\")]\n", + "----\n", + "[HumanMessage(content='what is the weather in sf'), AIMessage(content='', additional_kwargs={'function_call': {'name': 'tavily_search_results_json', 'arguments': '{\\n \"query\": \"weather in San Francisco\"\\n}'}}), FunctionMessage(content=\"[{'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 FranciscoData: 1991 - 2021 Min. Temperature °C (°F), Max. Temperature °C (°F), Precipitation / Rainfall mm (in), Humidity, Rainy days. Data: 1999 - 2019: avg. Sun hours San Francisco weather and climate for further months San Francisco in February San Francisco in March San Francisco in April San Francisco in May San Francisco in June San Francisco in July'}]\", name='tavily_search_results_json'), AIMessage(content=\"I'm sorry, but I couldn't find the current weather in San Francisco. However, you can visit [this website](https://en.climate-data.org/north-america/united-states-of-america/california/san-francisco-385/t/january-1/) to find information about the weather in San Francisco for the month of January.\")]\n", + "----\n" + ] + } + ], + "source": [ + "messages = [HumanMessage(content=\"what is the weather in sf\")]\n", + "for s in chain.stream(messages):\n", + " print(list(s.values())[0])\n", + " print(\"----\")" + ] + }, + { + "cell_type": "markdown", + "id": "4b003c09-ceeb-44bf-94c5-502bde98e3c1", + "metadata": {}, + "source": [ + "## Respond in a specific format" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "fac71b90-d7d0-4cab-af3b-f3ed8264472f", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_core.pydantic_v1 import BaseModel, Field\n", + "from typing import List" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "5d80baf8-977d-44b4-a092-9d449f16a577", + "metadata": {}, + "outputs": [], + "source": [ + "class Answer(BaseModel):\n", + " \"\"\"Final Response\"\"\"\n", + " temp: int = Field(description=\"current temperature, in Farenheit\")\n", + " source: List[str] = Field(description=\"URLs to go to to learn more info\")" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "ead5fb7e-ad02-4554-9595-2cbac99207a8", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_core.utils.function_calling import convert_pydantic_to_openai_function\n", + "model = ChatOpenAI().bind_functions([format_tool_to_openai_function(t) for t in tools] + [convert_pydantic_to_openai_function(Answer)])" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "8e708efa-501e-42e6-94bd-71f72b61ec48", + "metadata": {}, + "outputs": [], + "source": [ + "def call_model(messages):\n", + " response = model.invoke(messages)\n", + " return messages + [response]\n", + "\n", + "\n", + "def exit(messages):\n", + " last_message = messages[-1]\n", + " if \"function_call\" not in last_message.additional_kwargs:\n", + " return \"end\"\n", + " elif \"function_call\" in last_message.additional_kwargs and last_message.additional_kwargs[\"function_call\"][\"name\"] == \"Answer\":\n", + " return \"end\"\n", + " else:\n", + " return \"function\"" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "40264371-76ad-4216-8554-8afb7632d741", + "metadata": {}, + "outputs": [], + "source": [ + "chain = create_agent_messages_executor(call_model, call_tool, exit)" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "7f790767-3272-4b28-8264-ac621606be18", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[HumanMessage(content='what is the weather in sf'), AIMessage(content='', additional_kwargs={'function_call': {'name': 'tavily_search_results_json', 'arguments': '{\\n \"query\": \"current weather in San Francisco\"\\n}'}})]\n", + "----\n" + ] + }, + { + "name": "stdin", + "output_type": "stream", + "text": [ + "[y/n] Okay to call this tool? tool='tavily_search_results_json' tool_input={'query': 'current weather in San Francisco'} log='' \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[HumanMessage(content='what is the weather in sf'), AIMessage(content='', additional_kwargs={'function_call': {'name': 'tavily_search_results_json', 'arguments': '{\\n \"query\": \"current weather in San Francisco\"\\n}'}}), FunctionMessage(content=\"[{'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 FranciscoData: 1991 - 2021 Min. Temperature °C (°F), Max. Temperature °C (°F), Precipitation / Rainfall mm (in), Humidity, Rainy days. Data: 1999 - 2019: avg. Sun hours San Francisco weather and climate for further months San Francisco in February San Francisco in March San Francisco in April San Francisco in May San Francisco in June San Francisco in July'}]\", name='tavily_search_results_json')]\n", + "----\n", + "[HumanMessage(content='what is the weather in sf'), AIMessage(content='', additional_kwargs={'function_call': {'name': 'tavily_search_results_json', 'arguments': '{\\n \"query\": \"current weather in San Francisco\"\\n}'}}), FunctionMessage(content=\"[{'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 FranciscoData: 1991 - 2021 Min. Temperature °C (°F), Max. Temperature °C (°F), Precipitation / Rainfall mm (in), Humidity, Rainy days. Data: 1999 - 2019: avg. Sun hours San Francisco weather and climate for further months San Francisco in February San Francisco in March San Francisco in April San Francisco in May San Francisco in June San Francisco in July'}]\", name='tavily_search_results_json'), AIMessage(content='', additional_kwargs={'function_call': {'name': 'Answer', 'arguments': '{\\n \"temp\": 65,\\n \"source\": [\"https://en.climate-data.org/north-america/united-states-of-america/california/san-francisco-385/t/january-1/\"]\\n}'}})]\n", + "----\n", + "[HumanMessage(content='what is the weather in sf'), AIMessage(content='', additional_kwargs={'function_call': {'name': 'tavily_search_results_json', 'arguments': '{\\n \"query\": \"current weather in San Francisco\"\\n}'}}), FunctionMessage(content=\"[{'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 FranciscoData: 1991 - 2021 Min. Temperature °C (°F), Max. Temperature °C (°F), Precipitation / Rainfall mm (in), Humidity, Rainy days. Data: 1999 - 2019: avg. Sun hours San Francisco weather and climate for further months San Francisco in February San Francisco in March San Francisco in April San Francisco in May San Francisco in June San Francisco in July'}]\", name='tavily_search_results_json'), AIMessage(content='', additional_kwargs={'function_call': {'name': 'Answer', 'arguments': '{\\n \"temp\": 65,\\n \"source\": [\"https://en.climate-data.org/north-america/united-states-of-america/california/san-francisco-385/t/january-1/\"]\\n}'}})]\n", + "----\n" + ] + } + ], + "source": [ + "messages = [HumanMessage(content=\"what is the weather in sf\")]\n", + "for s in chain.stream(messages):\n", + " print(list(s.values())[0])\n", + " print(\"----\")" + ] + }, + { + "cell_type": "markdown", + "id": "ddc74d57-b82a-406d-9f19-ef7808ee9ceb", + "metadata": {}, + "source": [ + "## Tool State" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "bacb638e-5bd5-425c-a019-b8345a24ef17", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_core.tools import tool\n", + "from langchain_core.pydantic_v1 import BaseModel, Field" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "e3e2a10a-b1c9-4320-8f01-a42b9c6b132d", + "metadata": {}, + "outputs": [], + "source": [ + "class IntSchema(BaseModel):\n", + " num: int\n", + " ls: dict\n", + "\n", + " @classmethod\n", + " def schema(cls):\n", + " schema = super().schema()\n", + " properties = schema.get('properties', {})\n", + " properties.pop('ls', None) # Remove the hidden attribute\n", + " return schema" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "3bd82332-4f47-42b8-b70b-b8b2dedac19c", + "metadata": {}, + "outputs": [], + "source": [ + "@tool(args_schema=IntSchema)\n", + "def add_int(num, ls):\n", + " \"\"\"Call this to add number to the list.\"\"\"\n", + " ls[\"foo\"].append(num)\n", + " print(ls)\n", + " return \"Done!\"" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "2a8d4bed-04c8-4bf2-ac84-5356cbd07d3a", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'num': {'title': 'Num', 'type': 'integer'}}" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "IntSchema.schema()[\"properties\"]" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "9471f29b-0c10-4d07-8d15-f3463c453fcb", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/harrisonchase/workplace/langchain/libs/core/langchain_core/_api/deprecation.py:117: LangChainDeprecationWarning: The class `langchain_community.chat_models.openai.ChatOpenAI` was deprecated in langchain-community 0.0.10 and will be removed in 0.2.0. An updated version of the class exists in the langchain-openai package and should be used instead. To use it run `pip install -U langchain-openai` and import as `from langchain_openai import ChatOpenAI`.\n", + " warn_deprecated(\n" + ] + } + ], + "source": [ + "from langchain_core.agents import AgentAction\n", + "from langchain_core.messages import FunctionMessage, HumanMessage, SystemMessage\n", + "from langchain.tools.render import format_tool_to_openai_function\n", + "import json\n", + "from langchain.chat_models import ChatOpenAI\n", + "\n", + "from langchain_community.tools.tavily_search import TavilySearchResults\n", + "\n", + "from langgraph.prebuilt.tool_executor import ToolExecutor\n", + "from langgraph.prebuilt.executor import create_executor\n", + "tools = [add_int]\n", + "model = ChatOpenAI().bind_functions([format_tool_to_openai_function(t) for t in tools])" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "bf64ec0f-4563-446a-952b-c9ad3016d063", + "metadata": {}, + "outputs": [], + "source": [ + "ls = {\"foo\": []}\n", + "def call_model(messages):\n", + " response = model.invoke(messages)\n", + " return messages + [response]\n", + "\n", + "\n", + "def exit(messages):\n", + " last_message = messages[-1]\n", + " if \"function_call\" not in last_message.additional_kwargs:\n", + " return \"end\"\n", + " else:\n", + " return \"continue\"\n", + "\n", + "tool_executor = ToolExecutor(tools)\n", + "def call_tool(messages):\n", + " last_message = messages[-1]\n", + " agent_action = AgentAction(\n", + " tool=last_message.additional_kwargs[\"function_call\"][\"name\"],\n", + " tool_input=json.loads(last_message.additional_kwargs[\"function_call\"][\"arguments\"]),\n", + " log=\"\",\n", + " )\n", + " agent_action.tool_input[\"ls\"] = ls\n", + " tool_to_use = {t.name: t for t in tools}[agent_action.tool]\n", + " # Call that tool on the input\n", + " observation = tool_to_use.invoke(agent_action.tool_input)\n", + " function_message = FunctionMessage(content=str(observation), name=agent_action.tool)\n", + " return messages + [function_message]" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "83e7429b-1981-44ec-b567-50aa3a7b1731", + "metadata": {}, + "outputs": [], + "source": [ + "chain = create_executor(call_model, call_tool, exit)" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "bda94e2c-9cf7-49b4-b687-466f1cbf81b2", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'foo': []}" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "ls" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "9b63e7d1-a421-4a6b-ad47-93d8b497fd3e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[HumanMessage(content='add the number one to the list'), AIMessage(content='', additional_kwargs={'function_call': {'name': 'add_int', 'arguments': '{\\n \"num\": 1\\n}'}})]\n", + "----\n", + "{'foo': [1]}\n", + "[HumanMessage(content='add the number one to the list'), AIMessage(content='', additional_kwargs={'function_call': {'name': 'add_int', 'arguments': '{\\n \"num\": 1\\n}'}}), FunctionMessage(content='Done!', name='add_int')]\n", + "----\n", + "[HumanMessage(content='add the number one to the list'), AIMessage(content='', additional_kwargs={'function_call': {'name': 'add_int', 'arguments': '{\\n \"num\": 1\\n}'}}), FunctionMessage(content='Done!', name='add_int'), AIMessage(content='The number one has been added to the list.')]\n", + "----\n", + "[HumanMessage(content='add the number one to the list'), AIMessage(content='', additional_kwargs={'function_call': {'name': 'add_int', 'arguments': '{\\n \"num\": 1\\n}'}}), FunctionMessage(content='Done!', name='add_int'), AIMessage(content='The number one has been added to the list.')]\n", + "----\n" + ] + } + ], + "source": [ + "messages = [HumanMessage(content=\"add the number one to the list\")]\n", + "for s in chain.stream(messages):\n", + " print(list(s.values())[0])\n", + " print(\"----\")" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "b2bbf236-c6b1-472c-a1a6-64d057096584", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'foo': [1]}" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "ls" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "7679dc19-8ee4-4139-b2bd-2773ed378193", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[HumanMessage(content='add the number 3 to the list'), AIMessage(content='', additional_kwargs={'function_call': {'name': 'add_int', 'arguments': '{\\n \"num\": 3\\n}'}})]\n", + "----\n", + "{'foo': [1, 3]}\n", + "[HumanMessage(content='add the number 3 to the list'), AIMessage(content='', additional_kwargs={'function_call': {'name': 'add_int', 'arguments': '{\\n \"num\": 3\\n}'}}), FunctionMessage(content='Done!', name='add_int')]\n", + "----\n", + "[HumanMessage(content='add the number 3 to the list'), AIMessage(content='', additional_kwargs={'function_call': {'name': 'add_int', 'arguments': '{\\n \"num\": 3\\n}'}}), FunctionMessage(content='Done!', name='add_int'), AIMessage(content='The number 3 has been added to the list.')]\n", + "----\n", + "[HumanMessage(content='add the number 3 to the list'), AIMessage(content='', additional_kwargs={'function_call': {'name': 'add_int', 'arguments': '{\\n \"num\": 3\\n}'}}), FunctionMessage(content='Done!', name='add_int'), AIMessage(content='The number 3 has been added to the list.')]\n", + "----\n" + ] + } + ], + "source": [ + "messages = [HumanMessage(content=\"add the number 3 to the list\")]\n", + "for s in chain.stream(messages):\n", + " print(list(s.values())[0])\n", + " print(\"----\")" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "acb104ef-270d-4a03-b14c-c01b8e391935", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'foo': [1, 3]}" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "ls" + ] + }, + { + "cell_type": "markdown", + "id": "592c3886-71d1-4539-80dd-111e55cc3a85", + "metadata": {}, + "source": [ + "## Reflexion Agent" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "f6f96e81-4a20-4599-a625-8d18df6fa76d", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain.agents import AgentExecutor, BaseMultiActionAgent, Tool\n", + "from langchain.schema import AgentAction, AgentFinish\n", + "from langchain_core.language_models.chat_models import BaseChatModel\n", + "from langchain.chains import LLMChain\n", + "\n", + "from langchain.globals import set_llm_cache\n", + "\n", + "from dotenv import load_dotenv\n", + "\n", + "from pydantic import BaseModel\n", + "\n", + "from langchain.chat_models import ChatOpenAI\n", + "from langchain.cache import SQLiteCache\n", + "\n", + "from langchain_core.output_parsers import BaseOutputParser\n", + "\n", + "from langchain.prompts.chat import ChatPromptTemplate\n", + "from langchain.callbacks import get_openai_callback\n", + "from langchain.tools.tavily_search import TavilySearchResults\n", + "from langchain.utilities.tavily_search import TavilySearchAPIWrapper\n", + "from langchain.pydantic_v1 import BaseModel\n", + "import os\n", + "\n", + "from langchain.agents import AgentType, initialize_agent, load_tools\n", + "\n", + "set_llm_cache(SQLiteCache(database_path=\".langchain.db\"))\n", + "\n", + "\n", + "llm = ChatOpenAI(\n", + " temperature=0.0,\n", + " max_tokens=2000,\n", + " max_retries=100,\n", + " model=\"gpt-4-1106-preview\",\n", + ")\n", + "\n", + "search = TavilySearchAPIWrapper()\n", + "tavily_tool = TavilySearchResults(api_wrapper=search, max_results=5)\n", + "\n", + "NEXT_STEP_TEMPLATE = \"\"\"You are expert researcher trying answer a question ~250 words. You are asked to answer the following question: {question}\n", + "\n", + "The way you are going to answer the question is as follows:\n", + "\n", + "1. Revise your previous answer using the new information.\n", + " - You should use the previous critique to add important information to your answer.\n", + " _ You MUST include numerical citations in your revised answer to ensure it can be verified.\n", + " - Add a \"References\" section to the bottom of your answer (which does not count towards the word limit). In form of:\n", + " - [1] https://example.com\n", + " - [2] https://example.com\n", + " - You should use the previous critique to remove superfluous information from your answer and make SURE it is not more than 250 words.\n", + "2. Reflect and critique your answer. Specifically, you should:\n", + " - Think about what is missing from your answer.\n", + " - Think about what is superfluous in your answer.\n", + " - Think about what search query you should use next to improve your answer.\n", + " Give your answer in exactly 2 parts. The first should address what is missing from your answer. The second should address what could be removed from your answer. Your should be VERY harsh as we really want to improve the answer.\n", + "3. Give the search query you came up with to improve your answer.\n", + "\n", + "Previous steps: \n", + "\n", + "{previous_steps}\n", + "\n", + "===\n", + "\n", + "Format your answer as follows:\n", + "\n", + "Revised answer: [give your revised answer based on the previous critique and new information from the search engine then the \"References\" section]\n", + "Critique: [give your harsh critique of your revised answer in 2 parts: what is missing and what is superfluous]\n", + "Search query: [give the new search query you came up with to enter into the search engine to improve your answer. If you have more than one, make sure they are comma separated and in quotes]\n", + "\n", + "SAY NOTHING else please.\"\"\"\n", + "\n", + "INITIAL_ANSWER_TEMPLATE = \"\"\"You are expert researcher trying answer a question ~250 words. You are asked to answer the following question: {question}\n", + "\n", + "The way you are going to answer the question is as follows:\n", + "\n", + "1. Give a detailed in ~250 words.\n", + "2. Reflect and critique your answer. Specifically, you should:\n", + " - Think about what is missing from your answer.\n", + " - Think about what is superfluous in your answer.\n", + " - Think about what search query you should use next to improve your answer.\n", + " Give your answer in exactly 2 parts. The first should address what is missing from your answer. The second should address what could be removed from your answer. Your should be VERY harsh as we really want to improve the answer.\n", + "3. Give the search query you came up with to improve your answer.\n", + "\n", + "===\n", + "\n", + "Format your answer as follows:\n", + "\n", + "Answer: [give your initial answer]\n", + "Critique: [give your harsh critique of your answer in 2 parts: what is missing and what is superfluous]\n", + "Search query: [give the search query you came up with to improve your answer. If you have more than one, make sure they are comma separated and in quotes]\n", + "\n", + "SAY NOTHING else please.\"\"\"\n", + "\n", + "\n", + "class ReflexionStep(BaseModel):\n", + " \"\"\"A single step in the reflexion process.\"\"\"\n", + "\n", + " answer: str\n", + " critique: str\n", + " search_query: str\n", + "\n", + " def __str__(self):\n", + " return f\"Answer: {self.answer}\\nCritique: {self.critique}\\nSearch query: {self.search_query}\"\n", + "\n", + "def _parse_reflexion_step(output: str) -> tuple[str, str, str]:\n", + " # find answer using .split()\n", + " if (\"Answer:\" not in output and \"Revised answer:\" not in output) or not \"Critique:\" in output or not \"Search query:\" in output:\n", + " raise ValueError(f\"The output is not formatted correctly. Output: {output}\")\n", + " if \"Answer:\" in output:\n", + " answer = output.split(\"Answer:\")[1].split(\"Critique:\")[0].strip()\n", + " else:\n", + " answer = output.split(\"Revised answer:\")[1].split(\"Critique:\")[0].strip()\n", + " critique = output.split(\"Critique:\")[1].split(\"Search query:\")[0].strip()\n", + " search_query = output.split(\"Search query:\")[1].strip()\n", + " return answer, critique, search_query\n", + "\n", + "class ReflexionStepParser(BaseOutputParser[ReflexionStep]):\n", + " \"\"\"Parser for the reflexion step.\"\"\"\n", + "\n", + " def parse(self, output: str) -> ReflexionStep:\n", + " \"\"\"Parse the output.\"\"\"\n", + " # try to find answer or initial answer\n", + " answer, critique, search_query = _parse_reflexion_step(output)\n", + " return ReflexionStep(\n", + " answer=answer, critique=critique, search_query=search_query\n", + " )" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "7708fa95-547b-4bea-b126-3656de7d5873", + "metadata": {}, + "outputs": [], + "source": [ + "initial_chain = RunnablePassthrough.assign(\n", + " agent_outcome = ChatPromptTemplate.from_template(INITIAL_ANSWER_TEMPLATE) | llm | ReflexionStepParser() | (lambda x: AgentAction(\n", + " tool=\"tavily_search_results_json\",\n", + " tool_input=x.search_query,\n", + " log=str(x),\n", + " ))\n", + ")\n", + "\n", + "def prep_next(inputs):\n", + " intermediate_steps = inputs[\"intermediate_steps\"]\n", + " previous_steps = list[str]()\n", + "\n", + " for i, (action, observation) in enumerate(intermediate_steps, start=1):\n", + " last_step_str = f\"\"\"Step {i}:\n", + "\n", + "{action.log}\n", + "\n", + "Search output for \"{action.tool_input}\":\n", + "\n", + "{observation}\"\"\"\n", + " previous_steps.append(last_step_str)\n", + "\n", + " previous_steps_str = \"\\n\\n\".join(previous_steps)\n", + " inputs[\"previous_steps\"] = previous_steps_str\n", + " return inputs\n", + " \n", + "next_chain = RunnablePassthrough.assign(\n", + " agent_outcome = prep_next | ChatPromptTemplate.from_template(NEXT_STEP_TEMPLATE) | llm | ReflexionStepParser() | (lambda x: AgentAction(\n", + " tool=\"tavily_search_results_json\",\n", + " tool_input=x.search_query,\n", + " log=str(x),\n", + " ))\n", + ")\n", + "\n", + "def finish(inputs):\n", + " intermediate_steps = inputs[\"intermediate_steps\"]\n", + " last_action, _ = intermediate_steps[-1]\n", + " last_step_str = last_action.log\n", + " # extract answer\n", + " answer, _, _ = _parse_reflexion_step(last_step_str)\n", + "\n", + " first_action, _ = intermediate_steps[0]\n", + " first_step_str = first_action.log\n", + " # extract answer\n", + " initial_answer, _, _ = _parse_reflexion_step(first_step_str)\n", + "\n", + " return AgentFinish(\n", + " log=\"Reached max steps.\",\n", + " return_values={\"output\": answer, \"initial_answer\": initial_answer},\n", + " )\n", + "\n", + "\n", + "def execute_tools(data):\n", + " agent_action = data.pop('agent_outcome')\n", + " observation = {t.name: t for t in tools}[agent_action.tool].invoke(agent_action.tool_input)\n", + " data['intermediate_steps'].append((agent_action, observation))\n", + " return data\n" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "d6cdd1cd-e480-4dd7-99b4-9018eb243b4d", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "AgentFinish(return_values={'output': \"The current weather in San Francisco can be accessed through various weather reporting services, which provide real-time temperature, humidity, wind, and chances of precipitation [1]. Historically, San Francisco experiences a mild, Mediterranean climate with average temperatures ranging from the low 50s to the mid-60s Fahrenheit. The city's unique topography creates microclimates, leading to significant weather variations across different neighborhoods. San Francisco's summers are notably cooler compared to other Californian cities, largely due to the cold California Current and persistent fog, especially in June and July. Winters are mild and the wettest months span from November to March, with an annual rainfall average of approximately 23 inches. Wind is a prominent feature, with spring being particularly windy. For historical weather extremes and average wind speeds, additional specific data can be sought from climatological records.\\n\\nReferences:\\n[1] https://www.weather25.com/north-america/usa/california/san-francisco\", 'initial_answer': \"The weather in San Francisco (SF) is characterized by a mild, Mediterranean-like climate with wet winters and dry summers. The city's unique topography and coastal location result in microclimates, where weather conditions can vary significantly from one neighborhood to another. Average temperatures typically range from the low 50s to the mid-60s Fahrenheit throughout the year. Summers in San Francisco are often cooler than in other parts of California due to the cold California Current offshore and the presence of fog, particularly in June and July. The fog usually burns off by the afternoon, leading to clearer skies and slightly warmer temperatures. Winters are mild and moist, with the majority of the city's rainfall occurring between November and March. Rainfall averages around 23 inches annually. Wind is also a notable feature of San Francisco's weather, with spring being the windiest season. Despite the general patterns, it's always advisable to dress in layers due to the potential for rapid weather changes.\"}, log='Reached max steps.')" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "workflow = Graph()\n", + "\n", + "# add actors\n", + "workflow.add_node(\"initial\", initial_chain)\n", + "workflow.add_node(\"next\", next_chain)\n", + "workflow.add_node(\"finish\", finish)\n", + "workflow.add_node(\"tools\", execute_tools)\n", + "\n", + "# Enter with initial actor, then loop through tools -> next steps until finished\n", + "workflow.set_entry_point('initial')\n", + "\n", + "workflow.add_edge('initial', 'tools')\n", + "workflow.add_conditional_edges(\n", + " 'tools',\n", + " lambda x: \"exit\" if len(x['intermediate_steps']) >= 2 else \"continue\",\n", + " {\n", + " \"continue\": 'next',\n", + " \"exit\": 'finish'\n", + " }\n", + ")\n", + "workflow.add_edge('next', 'tools')\n", + "workflow.set_finish_point('finish')\n", + "\n", + "chain = workflow.compile()\n", + "\n", + "chain.invoke({\"question\": \"what is the weather in sf\", \"intermediate_steps\": []})" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9babf196-b1fd-492d-9197-96a674f5e81d", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0c349b42-ae43-4564-90d2-80eff5b9c236", + "metadata": {}, + "outputs": [], + "source": [ + "## Plan and Execute\n", + "\n", + "from langchain_core.pydantic_v1 import BaseModel, Field\n", + "from typing import List, Tuple\n", + "\n", + "\n", + "class PlanExecute(BaseModel):\n", + "\n", + " plan: List[str] = []\n", + " past_steps: List[Tuple] = []\n", + " response: str = \"\"\n", + "\n", + "from langchain_core.prompts import ChatPromptTemplate\n", + "from langchain_openai import ChatOpenAI\n", + "from langchain.chains.openai_functions import create_structured_output_runnable\n", + "from langchain_core.tools import tool\n", + "\n", + "class Plan(BaseModel):\n", + " \"\"\"Plan to follow in future\"\"\"\n", + " steps: List[str] = Field(description=\"different steps to follow, should be in sorted order\")\n", + "\n", + "planner_prompt = ChatPromptTemplate.from_template(\"\"\"For the given objective, come up with a simple step by step plan. \\\n", + "This plan should involve individual tasks, that if executed correctly will yield the correct answer. Do not add any superfluous steps. \\\n", + "The result of the final step should be the final answer. Make sure that each step has all the information needed - do not skip steps.\n", + "\n", + "{objective}\"\"\")\n", + "planner = create_structured_output_runnable(Plan, ChatOpenAI(model=\"gpt-4-1106-preview\", temperature=0), planner_prompt)\n", + "\n", + "planner.invoke({'objective': 'what is leo dicaprios gf age raised to .23'})\n", + "\n", + "@tool\n", + "def search(query:str):\n", + " \"\"\"Get a response from google\"\"\"\n", + " return 25\n", + "\n", + "@tool\n", + "def math(equation: str):\n", + " \"\"\"Solve a math equation\"\"\"\n", + " return .34\n", + "\n", + "tools = [search, math]\n", + "\n", + "from langchain import hub\n", + "from langchain.agents import create_openai_functions_agent\n", + "# Get the prompt to use - you can modify this!\n", + "prompt = hub.pull(\"hwchase17/openai-functions-agent\")\n", + "# Choose the LLM that will drive the agent\n", + "llm = ChatOpenAI(model=\"gpt-3.5-turbo-1106\")\n", + "# Construct the OpenAI Functions agent\n", + "agent_runnable = create_openai_functions_agent(llm, tools, prompt)\n", + "\n", + "from langchain_core.runnables import RunnablePassthrough\n", + "from langchain_core.agents import AgentFinish\n", + "\n", + "\n", + "# Define the agent\n", + "# Note that here, we are using `.assign` to add the output of the agent to the dictionary\n", + "# This dictionary will be returned from the node\n", + "# The reason we don't want to return just the result of `agent_runnable` from this node is\n", + "# that we want to continue passing around all the other inputs\n", + "agent = RunnablePassthrough.assign(\n", + " agent_outcome = agent_runnable\n", + ")\n", + "\n", + "# Define the function to execute tools\n", + "def action(data):\n", + " # Get the most recent agent_outcome - this is the key added in the `agent` above\n", + " agent_action = data.pop('agent_outcome')\n", + " # Get the tool to use\n", + " tool_to_use = {t.name: t for t in tools}[agent_action.tool]\n", + " # Call that tool on the input\n", + " observation = tool_to_use.invoke(agent_action.tool_input)\n", + " # We now add in the action and the observation to the `intermediate_steps` list\n", + " # This is the list of all previous actions taken and their output\n", + " data['intermediate_steps'].append((agent_action, observation))\n", + " return data\n", + "\n", + "# Define logic that will be used to determine which conditional edge to go down\n", + "def should_continue(data):\n", + " # If the agent outcome is an AgentFinish, then we return `exit` string\n", + " # This will be used when setting up the graph to define the flow\n", + " if isinstance(data['agent_outcome'], AgentFinish):\n", + " return \"end\"\n", + " # Otherwise, an AgentAction is returned\n", + " # Here we return `continue` string\n", + " # This will be used when setting up the graph to define the flow\n", + " else:\n", + " return \"continue\"\n", + "\n", + "def plan(inputs):\n", + " inputs['state'] = PlanExecute(plan=planner.invoke(inputs).steps)\n", + " inputs['input'] = inputs['state'].plan[0]\n", + " inputs['intermediate_steps'] = []\n", + " return inputs\n", + "\n", + "from langchain.chains.openai_functions import create_openai_fn_runnable\n", + "class Response(BaseModel):\n", + " \"\"\"Response to user.\"\"\"\n", + " response: str\n", + "\n", + "replanner_prompt = ChatPromptTemplate.from_template(\"\"\"For the given objective, come up with a simple step by step plan. \\\n", + "This plan should involve individual tasks, that if executed correctly will yield the correct answer. Do not add any superfluous steps. \\\n", + "The result of the final step should be the final answer. Make sure that each step has all the information needed - do not skip steps.\n", + "\n", + "Your objective was this:\n", + "{objective}\n", + "\n", + "Your original plan was this:\n", + "{plan}\n", + "\n", + "You have currently done the follow steps:\n", + "{steps}\n", + "\n", + "Update your plan accordingly. If no more steps are needed and you can return to the user, then respond with that. Otherwise, fill out the plan.\"\"\")\n", + "\n", + "\n", + "replanner = create_openai_fn_runnable([Plan, Response], ChatOpenAI(model=\"gpt-4-1106-preview\", temperature=0), replanner_prompt)\n", + "\n", + "replanner.invoke({\"objective\": \"look up the temperature\", \"plan\": \"look up the temperature\", \"steps\": [(\"look up the temperature\", \"i am in san diego\")]})\n", + "\n", + "def replan(inputs):\n", + " inputs['state'].past_steps.append((inputs['state'].plan[0], inputs['agent_outcome'].return_values['output']))\n", + " sub_inputs = {\n", + " \"objective\": inputs[\"objective\"],\n", + " \"plan\": inputs[\"state\"].plan,\n", + " \"steps\": inputs[\"state\"].past_steps\n", + " }\n", + " output = replanner.invoke(sub_inputs)\n", + " if isinstance(output, Response):\n", + " inputs['state'].response = output.response\n", + " else:\n", + " inputs['state'].plan = output.steps\n", + " inputs['input'] = inputs['state'].plan[0]\n", + " return inputs\n", + "\n", + "\n", + "def should_end(inputs):\n", + " if inputs['state'].response:\n", + " return True\n", + " else:\n", + " return False\n", + "\n", + "from langgraph.graph import END, Graph\n", + "\n", + "workflow = Graph()\n", + "\n", + "# Add the plan node\n", + "workflow.add_node(\"plan\", plan)\n", + "\n", + "# Add the agent node, we give it name `agent` which we will use later\n", + "workflow.add_node(\"agent\", agent)\n", + "# Add the action node, we give it name `action` which we will use later\n", + "workflow.add_node(\"action\", action)\n", + "\n", + "# Add a replan node\n", + "workflow.add_node(\"replan\", replan)\n", + "\n", + "# Set the entrypoint as `agent`\n", + "# This means that this node is the first one called\n", + "workflow.set_entry_point(\"plan\")\n", + "\n", + "# We now add a conditional edge\n", + "workflow.add_conditional_edges(\n", + " # First, we define the start node. We use `agent`.\n", + " # This means these are the edges taken after the `agent` node is called.\n", + " \"agent\",\n", + " # Next, we pass in the function that will determine which node is called next.\n", + " should_continue,\n", + " # Finally we pass in a mapping.\n", + " # The keys are strings, and the values are other nodes.\n", + " # END is a special node marking that the graph should finish.\n", + " # What will happen is we will call `should_continue`, and then the output of that\n", + " # will be matched against the keys in this mapping.\n", + " # Based on which one it matches, that node will then be called.\n", + " {\n", + " # If `tools`, then we call the tool node.\n", + " \"continue\": \"action\",\n", + " # Otherwise we go back to replan\n", + " \"end\": \"replan\"\n", + " }\n", + ")\n", + "\n", + "# From plan we go to agent\n", + "workflow.add_edge('plan', 'agent')\n", + "\n", + "# We now add a normal edge from `tools` to `agent`.\n", + "# This means that after `tools` is called, `agent` node is called next.\n", + "workflow.add_edge('action', 'agent')\n", + "\n", + "workflow.add_conditional_edges(\n", + " \"replan\",\n", + " # Next, we pass in the function that will determine which node is called next.\n", + " should_end,\n", + " {\n", + " # If `tools`, then we call the tool node.\n", + " True: END,\n", + " False: \"agent\",\n", + " }\n", + ")\n", + "\n", + "# Finally, we compile it!\n", + "# This compiles it into a LangChain Runnable,\n", + "# meaning you can use it as you would any other runnable\n", + "chain = workflow.compile()\n", + "\n", + "for s in chain.stream({\"objective\": \"what is leo dicaprios gf age raised to .34\"}):\n", + " print(s)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bb6fb6a8-f6f5-444a-9033-ef9e0bd6fd23", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.1" + } + }, + "nbformat": 4, + "nbformat_minor": 5 } diff --git a/examples/messages_executor.ipynb b/examples/messages_executor.ipynb new file mode 100644 index 000000000..e0e145a98 --- /dev/null +++ b/examples/messages_executor.ipynb @@ -0,0 +1,96 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "efb7e3c0-c63f-40f6-93ce-19681d650fc2", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain import hub\n", + "from langchain_openai import ChatOpenAI\n", + "from langchain_community.tools.tavily_search import TavilySearchResults\n", + "from langgraph.prebuilt.messages_executor import create_messages_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": "code", + "execution_count": 3, + "id": "32b4ae66-f667-4a8b-a602-503fd0effcd9", + "metadata": {}, + "outputs": [], + "source": [ + "app = create_messages_executor(model, tools)" + ] + }, + { + "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': '{\\n \"query\": \"weather in San Francisco\"\\n}', 'name': 'tavily_search_results_json'}})]}\n", + "----\n", + "{'messages': [FunctionMessage(content=\"[{'url': 'https://weatherspark.com/h/m/557/2024/1/Historical-Weather-in-January-2024-in-San-Francisco-California-United-States', 'content': 'January 2024 Weather History in San Francisco California, United States Daily Precipitation in January 2024 in San Francisco Observed Weather in January 2024 in San Francisco San Francisco Temperature History January 2024 Hourly Temperature in January 2024 in San Francisco Hours of Daylight and Twilight in January 2024 in San FranciscoThis report shows the past weather for San Francisco, providing a weather history for January 2024. It features all historical weather data series we have available, including the San Francisco temperature history for January 2024. You can drill down from year to month and even day level reports by clicking on the graphs.'}]\", name='tavily_search_results_json')]}\n", + "----\n", + "{'messages': [AIMessage(content=\"I apologize, but I couldn't retrieve the current weather information for San Francisco. However, you can check the weather history for January 2024 in San Francisco on this website: [San Francisco Weather History](https://weatherspark.com/h/m/557/2024/1/Historical-Weather-in-January-2024-in-San-Francisco-California-United-States).\")]}\n", + "----\n", + "{'messages': [HumanMessage(content='what is the weather in sf'), AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\\n \"query\": \"weather in San Francisco\"\\n}', 'name': 'tavily_search_results_json'}}), FunctionMessage(content=\"[{'url': 'https://weatherspark.com/h/m/557/2024/1/Historical-Weather-in-January-2024-in-San-Francisco-California-United-States', 'content': 'January 2024 Weather History in San Francisco California, United States Daily Precipitation in January 2024 in San Francisco Observed Weather in January 2024 in San Francisco San Francisco Temperature History January 2024 Hourly Temperature in January 2024 in San Francisco Hours of Daylight and Twilight in January 2024 in San FranciscoThis report shows the past weather for San Francisco, providing a weather history for January 2024. It features all historical weather data series we have available, including the San Francisco temperature history for January 2024. You can drill down from year to month and even day level reports by clicking on the graphs.'}]\", name='tavily_search_results_json'), AIMessage(content=\"I apologize, but I couldn't retrieve the current weather information for San Francisco. However, you can check the weather history for January 2024 in San Francisco on this website: [San Francisco Weather History](https://weatherspark.com/h/m/557/2024/1/Historical-Weather-in-January-2024-in-San-Francisco-California-United-States).\")]}\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": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.1" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/messages_executor_how_to/base.ipynb b/examples/messages_executor_how_to/base.ipynb new file mode 100644 index 000000000..8b6397135 --- /dev/null +++ b/examples/messages_executor_how_to/base.ipynb @@ -0,0 +1,235 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "aa9f9110-ea74-43af-aa72-6b45518abd6e", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain import hub\n", + "from langchain_openai import ChatOpenAI\n", + "from langchain_community.tools.tavily_search import TavilySearchResults\n", + "from langgraph.prebuilt.messages_executor import create_messages_executor\n", + "from langchain_core.messages import HumanMessage" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "0f005b2d-ddc0-4d60-8af9-9eb3dbeb45b7", + "metadata": {}, + "outputs": [], + "source": [ + "tools = [TavilySearchResults(max_results=1)]\n", + "model = ChatOpenAI()" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "02831581-f1ba-4701-ba33-f0f68468907d", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain.tools.render import format_tool_to_openai_function" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "a5c0ca12-4922-461c-b740-62ff99f8ae56", + "metadata": {}, + "outputs": [], + "source": [ + "model = model.bind_functions([format_tool_to_openai_function(t) for t in tools])" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "57547622-ddd8-4179-aa4a-e1b69ca4523c", + "metadata": {}, + "outputs": [], + "source": [ + "from langgraph.prebuilt.tool_executor import ToolExecutor\n", + "tool_executor = ToolExecutor(tools)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "96141ebd-32af-4c9d-a0b0-f46482b8bb88", + "metadata": {}, + "outputs": [], + "source": [ + "from typing import TypedDict, Annotated, Sequence\n", + "import operator\n", + "from langchain_core.messages import BaseMessage\n", + "# We create the AgentState that we will pass around\n", + "# This simply involves a list of messages\n", + "# We want steps to return messages to append to the list\n", + "# So we annotate the messages attribute with operator.add\n", + "class AgentState(TypedDict):\n", + " messages: Annotated[Sequence[BaseMessage], operator.add]" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "f422dc99-672e-414f-88b3-1a6dd21b6882", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_core.agents import AgentAction\n", + "import json\n", + "from langchain_core.messages import FunctionMessage\n", + "\n", + "# Define the function that determines whether to continue or not\n", + "def should_continue(state):\n", + " messages = state['messages']\n", + " last_message = messages[-1]\n", + " # If there is no function call, then we finish\n", + " if \"function_call\" not in last_message.additional_kwargs:\n", + " return \"end\"\n", + " # Otherwise if there is, we continue\n", + " else:\n", + " return \"continue\"\n", + "\n", + "# Define the function that calls the model\n", + "def call_model(state):\n", + " messages = state['messages']\n", + " response = model.invoke(messages)\n", + " # We return a list, because this will get added to the existing list\n", + " return {\"messages\": [response]}\n", + "\n", + "# Define the function to execute tools\n", + "def call_tool(state):\n", + " messages = state['messages']\n", + " # Based on the continue condition\n", + " # we know the last message involves a function call\n", + " last_message = messages[-1]\n", + " # We construct an AgentAction from the function_call\n", + " action = AgentAction(\n", + " tool=last_message.additional_kwargs[\"function_call\"][\"name\"],\n", + " tool_input=json.loads(last_message.additional_kwargs[\"function_call\"][\"arguments\"]),\n", + " log=\"\",\n", + " )\n", + " # We call the tool_executor and get back a response\n", + " response = tool_executor.invoke(action)\n", + " # We use the response to create a FunctionMessage\n", + " function_message = FunctionMessage(content=str(response), name=action.tool)\n", + " # We return a list, because this will get added to the existing list\n", + " return {\"messages\": [function_message]}\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "1133ec83-7af9-4444-9f88-c793fbdce214", + "metadata": {}, + "outputs": [], + "source": [ + "from langgraph.graph import StateGraph, END\n", + "# Define a new graph\n", + "workflow = StateGraph(AgentState)\n", + "\n", + "# Define the two nodes we will cycle between\n", + "workflow.add_node(\"agent\", call_model)\n", + "workflow.add_node(\"action\", call_tool)\n", + "\n", + "# Set the entrypoint as `agent`\n", + "# This means that this node is the first one called\n", + "workflow.set_entry_point(\"agent\")\n", + "\n", + "# We now add a conditional edge\n", + "workflow.add_conditional_edges(\n", + " # First, we define the start node. We use `agent`.\n", + " # This means these are the edges taken after the `agent` node is called.\n", + " \"agent\",\n", + " # Next, we pass in the function that will determine which node is called next.\n", + " should_continue,\n", + " # Finally we pass in a mapping.\n", + " # The keys are strings, and the values are other nodes.\n", + " # END is a special node marking that the graph should finish.\n", + " # What will happen is we will call `should_continue`, and then the output of that\n", + " # will be matched against the keys in this mapping.\n", + " # Based on which one it matches, that node will then be called.\n", + " {\n", + " # If `tools`, then we call the tool node.\n", + " \"continue\": \"action\",\n", + " # Otherwise we finish.\n", + " \"end\": END\n", + " }\n", + ")\n", + "\n", + "# We now add a normal edge from `tools` to `agent`.\n", + "# This means that after `tools` is called, `agent` node is called next.\n", + "workflow.add_edge('action', 'agent')\n", + "\n", + "# Finally, we compile it!\n", + "# This compiles it into a LangChain Runnable,\n", + "# meaning you can use it as you would any other runnable\n", + "app = workflow.compile()" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "289c648f-bfc6-464f-8df9-50be8b1b9e48", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'messages': [AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\\n \"query\": \"weather in San Francisco\"\\n}', 'name': 'tavily_search_results_json'}})]}\n", + "----\n", + "{'messages': [FunctionMessage(content=\"[{'url': 'https://weatherspark.com/h/m/557/2024/1/Historical-Weather-in-January-2024-in-San-Francisco-California-United-States', 'content': 'January 2024 Weather History in San Francisco California, United States Daily Precipitation in January 2024 in San Francisco Observed Weather in January 2024 in San Francisco San Francisco Temperature History January 2024 Hourly Temperature in January 2024 in San Francisco Hours of Daylight and Twilight in January 2024 in San Francisco80 deg, E Cloud Cover Mostly Cloudy 18,000 ft Mostly Clear 4,000 ft Partly Cloudy 15,000 ft Raw: KSFO 121956Z 08006KT 10SM FEW040 SCT150 BKN180 11/07 A3028 RMK AO2 SLP254 T01110067 This report shows the past weather for San Francisco, providing a weather history for January 2024.'}]\", name='tavily_search_results_json')]}\n", + "----\n", + "{'messages': [AIMessage(content='The weather in San Francisco is currently not available. However, you can check the weather history for January 2024 in San Francisco on [weatherspark.com](https://weatherspark.com/h/m/557/2024/1/Historical-Weather-in-January-2024-in-San-Francisco-California-United-States).')]}\n", + "----\n", + "{'messages': [HumanMessage(content='what is the weather in sf'), AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\\n \"query\": \"weather in San Francisco\"\\n}', 'name': 'tavily_search_results_json'}}), FunctionMessage(content=\"[{'url': 'https://weatherspark.com/h/m/557/2024/1/Historical-Weather-in-January-2024-in-San-Francisco-California-United-States', 'content': 'January 2024 Weather History in San Francisco California, United States Daily Precipitation in January 2024 in San Francisco Observed Weather in January 2024 in San Francisco San Francisco Temperature History January 2024 Hourly Temperature in January 2024 in San Francisco Hours of Daylight and Twilight in January 2024 in San Francisco80 deg, E Cloud Cover Mostly Cloudy 18,000 ft Mostly Clear 4,000 ft Partly Cloudy 15,000 ft Raw: KSFO 121956Z 08006KT 10SM FEW040 SCT150 BKN180 11/07 A3028 RMK AO2 SLP254 T01110067 This report shows the past weather for San Francisco, providing a weather history for January 2024.'}]\", name='tavily_search_results_json'), AIMessage(content='The weather in San Francisco is currently not available. However, you can check the weather history for January 2024 in San Francisco on [weatherspark.com](https://weatherspark.com/h/m/557/2024/1/Historical-Weather-in-January-2024-in-San-Francisco-California-United-States).')]}\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": "4cd4b39f-831a-4818-bf56-99cf301b0555", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.1" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/messages_executor_how_to/dynamically_returning_directly.ipynb b/examples/messages_executor_how_to/dynamically_returning_directly.ipynb new file mode 100644 index 000000000..6822c442d --- /dev/null +++ b/examples/messages_executor_how_to/dynamically_returning_directly.ipynb @@ -0,0 +1,302 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 2, + "id": "aa9f9110-ea74-43af-aa72-6b45518abd6e", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain import hub\n", + "from langchain_openai import ChatOpenAI\n", + "from langchain_community.tools.tavily_search import TavilySearchResults\n", + "from langgraph.prebuilt.messages_executor import create_messages_executor\n", + "from langchain_core.messages import HumanMessage" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "75bbcaa8-b23a-409c-9745-08d3aca7c3cb", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_core.pydantic_v1 import BaseModel, Field\n", + "\n", + "class SearchTool(search_tool.args_schema):\n", + " \"\"\"Look up things online, optionally returning directly\"\"\"\n", + " query: str = Field(description=\"query to look up online\")\n", + " return_direct: bool = Field(\n", + " description=\"Whether or the result of this should be returned directly to the user without you seeing what it is\", \n", + " default = False\n", + " )" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "0c93c6e1-532b-472e-b9f7-d374b9d3c89e", + "metadata": {}, + "outputs": [], + "source": [ + "search_tool = TavilySearchResults(max_results=1, args_schema=SearchTool)" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "0f005b2d-ddc0-4d60-8af9-9eb3dbeb45b7", + "metadata": {}, + "outputs": [], + "source": [ + "tools = [search_tool]\n", + "model = ChatOpenAI()" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "02831581-f1ba-4701-ba33-f0f68468907d", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain.tools.render import format_tool_to_openai_function" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "a5c0ca12-4922-461c-b740-62ff99f8ae56", + "metadata": {}, + "outputs": [], + "source": [ + "model = model.bind_functions([format_tool_to_openai_function(t) for t in tools])" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "57547622-ddd8-4179-aa4a-e1b69ca4523c", + "metadata": {}, + "outputs": [], + "source": [ + "from langgraph.prebuilt.tool_executor import ToolExecutor\n", + "tool_executor = ToolExecutor(tools)" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "96141ebd-32af-4c9d-a0b0-f46482b8bb88", + "metadata": {}, + "outputs": [], + "source": [ + "from typing import TypedDict, Annotated, Sequence\n", + "import operator\n", + "from langchain_core.messages import BaseMessage\n", + "# We create the AgentState that we will pass around\n", + "# This simply involves a list of messages\n", + "# We want steps to return messages to append to the list\n", + "# So we annotate the messages attribute with operator.add\n", + "class AgentState(TypedDict):\n", + " messages: Annotated[Sequence[BaseMessage], operator.add]" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "f422dc99-672e-414f-88b3-1a6dd21b6882", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_core.agents import AgentAction\n", + "import json\n", + "from langchain_core.messages import FunctionMessage\n", + "\n", + "# Define the function that determines whether to continue or not\n", + "def should_continue(state):\n", + " messages = state['messages']\n", + " last_message = messages[-1]\n", + " # If there is no function call, then we finish\n", + " if \"function_call\" not in last_message.additional_kwargs:\n", + " return \"end\"\n", + " # Otherwise if there is, we check if it's suppose to return direct\n", + " else:\n", + " arguments = json.loads(last_message.additional_kwargs[\"function_call\"][\"arguments\"])\n", + " if arguments.get(\"return_direct\", False):\n", + " return \"final\"\n", + " else:\n", + " return \"continue\"\n", + "\n", + "# Define the function that calls the model\n", + "def call_model(state):\n", + " messages = state['messages']\n", + " response = model.invoke(messages)\n", + " # We return a list, because this will get added to the existing list\n", + " return {\"messages\": [response]}\n", + "\n", + "# Define the function to execute tools\n", + "def call_tool(state):\n", + " messages = state['messages']\n", + " # Based on the continue condition\n", + " # we know the last message involves a function call\n", + " last_message = messages[-1]\n", + " tool_name = last_message.additional_kwargs[\"function_call\"][\"name\"]\n", + " arguments = json.loads(last_message.additional_kwargs[\"function_call\"][\"arguments\"])\n", + " if tool_name == \"tavily_search_results_json\":\n", + " if \"return_direct\" in arguments:\n", + " del arguments[\"return_direct\"]\n", + " # We construct an AgentAction from the function_call\n", + " action = AgentAction(\n", + " tool=tool_name,\n", + " tool_input=arguments,\n", + " log=\"\",\n", + " )\n", + " # We call the tool_executor and get back a response\n", + " response = tool_executor.invoke(action)\n", + " # We use the response to create a FunctionMessage\n", + " function_message = FunctionMessage(content=str(response), name=action.tool)\n", + " # We return a list, because this will get added to the existing list\n", + " return {\"messages\": [function_message]}\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "1133ec83-7af9-4444-9f88-c793fbdce214", + "metadata": {}, + "outputs": [], + "source": [ + "from langgraph.graph import StateGraph, END\n", + "# Define a new graph\n", + "workflow = StateGraph(AgentState)\n", + "\n", + "# Define the two nodes we will cycle between\n", + "workflow.add_node(\"agent\", call_model)\n", + "workflow.add_node(\"action\", call_tool)\n", + "workflow.add_node(\"final\", call_tool)\n", + "\n", + "# Set the entrypoint as `agent`\n", + "# This means that this node is the first one called\n", + "workflow.set_entry_point(\"agent\")\n", + "\n", + "# We now add a conditional edge\n", + "workflow.add_conditional_edges(\n", + " # First, we define the start node. We use `agent`.\n", + " # This means these are the edges taken after the `agent` node is called.\n", + " \"agent\",\n", + " # Next, we pass in the function that will determine which node is called next.\n", + " should_continue,\n", + " # Finally we pass in a mapping.\n", + " # The keys are strings, and the values are other nodes.\n", + " # END is a special node marking that the graph should finish.\n", + " # What will happen is we will call `should_continue`, and then the output of that\n", + " # will be matched against the keys in this mapping.\n", + " # Based on which one it matches, that node will then be called.\n", + " {\n", + " # If `tools`, then we call the tool node.\n", + " \"continue\": \"action\",\n", + " # Final call\n", + " \"final\": \"final\",\n", + " # Otherwise we finish.\n", + " \"end\": END\n", + " }\n", + ")\n", + "\n", + "# We now add a normal edge from `tools` to `agent`.\n", + "# This means that after `tools` is called, `agent` node is called next.\n", + "workflow.add_edge('action', 'agent')\n", + "workflow.add_edge('final', END)\n", + "\n", + "# Finally, we compile it!\n", + "# This compiles it into a LangChain Runnable,\n", + "# meaning you can use it as you would any other runnable\n", + "app = workflow.compile()" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "289c648f-bfc6-464f-8df9-50be8b1b9e48", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'messages': [AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\\n \"query\": \"weather in San Francisco\"\\n}', 'name': 'tavily_search_results_json'}})]}\n", + "----\n", + "{'messages': [FunctionMessage(content=\"[{'url': 'https://weatherspark.com/h/m/557/2024/1/Historical-Weather-in-January-2024-in-San-Francisco-California-United-States', 'content': 'January 2024 Weather History in San Francisco California, United States Daily Precipitation in January 2024 in San Francisco Observed Weather in January 2024 in San Francisco San Francisco Temperature History January 2024 Hourly Temperature in January 2024 in San Francisco Hours of Daylight and Twilight in January 2024 in San Francisco80 deg, E Cloud Cover Mostly Cloudy 18,000 ft Mostly Clear 4,000 ft Partly Cloudy 15,000 ft Raw: KSFO 121956Z 08006KT 10SM FEW040 SCT150 BKN180 11/07 A3028 RMK AO2 SLP254 T01110067 This report shows the past weather for San Francisco, providing a weather history for January 2024.'}]\", name='tavily_search_results_json')]}\n", + "----\n", + "{'messages': [AIMessage(content='The current weather in San Francisco is not available. However, you can check the weather history for January 2024 in San Francisco [here](https://weatherspark.com/h/m/557/2024/1/Historical-Weather-in-January-2024-in-San-Francisco-California-United-States).')]}\n", + "----\n", + "{'messages': [HumanMessage(content='what is the weather in sf'), AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\\n \"query\": \"weather in San Francisco\"\\n}', 'name': 'tavily_search_results_json'}}), FunctionMessage(content=\"[{'url': 'https://weatherspark.com/h/m/557/2024/1/Historical-Weather-in-January-2024-in-San-Francisco-California-United-States', 'content': 'January 2024 Weather History in San Francisco California, United States Daily Precipitation in January 2024 in San Francisco Observed Weather in January 2024 in San Francisco San Francisco Temperature History January 2024 Hourly Temperature in January 2024 in San Francisco Hours of Daylight and Twilight in January 2024 in San Francisco80 deg, E Cloud Cover Mostly Cloudy 18,000 ft Mostly Clear 4,000 ft Partly Cloudy 15,000 ft Raw: KSFO 121956Z 08006KT 10SM FEW040 SCT150 BKN180 11/07 A3028 RMK AO2 SLP254 T01110067 This report shows the past weather for San Francisco, providing a weather history for January 2024.'}]\", name='tavily_search_results_json'), AIMessage(content='The current weather in San Francisco is not available. However, you can check the weather history for January 2024 in San Francisco [here](https://weatherspark.com/h/m/557/2024/1/Historical-Weather-in-January-2024-in-San-Francisco-California-United-States).')]}\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": 27, + "id": "4cd4b39f-831a-4818-bf56-99cf301b0555", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'messages': [AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\\n \"query\": \"weather in San Francisco\",\\n \"return_direct\": true\\n}', 'name': 'tavily_search_results_json'}})]}\n", + "----\n", + "{'messages': [FunctionMessage(content=\"[{'url': 'https://weatherspark.com/h/m/557/2024/1/Historical-Weather-in-January-2024-in-San-Francisco-California-United-States', 'content': 'January 2024 Weather History in San Francisco California, United States Daily Precipitation in January 2024 in San Francisco Observed Weather in January 2024 in San Francisco San Francisco Temperature History January 2024 Hourly Temperature in January 2024 in San Francisco Hours of Daylight and Twilight in January 2024 in San Francisco80 deg, E Cloud Cover Mostly Cloudy 18,000 ft Mostly Clear 4,000 ft Partly Cloudy 15,000 ft Raw: KSFO 121956Z 08006KT 10SM FEW040 SCT150 BKN180 11/07 A3028 RMK AO2 SLP254 T01110067 This report shows the past weather for San Francisco, providing a weather history for January 2024.'}]\", name='tavily_search_results_json')]}\n", + "----\n", + "{'messages': [HumanMessage(content='what is the weather in sf? return this result directly by setting return_direct = True'), AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\\n \"query\": \"weather in San Francisco\",\\n \"return_direct\": true\\n}', 'name': 'tavily_search_results_json'}}), FunctionMessage(content=\"[{'url': 'https://weatherspark.com/h/m/557/2024/1/Historical-Weather-in-January-2024-in-San-Francisco-California-United-States', 'content': 'January 2024 Weather History in San Francisco California, United States Daily Precipitation in January 2024 in San Francisco Observed Weather in January 2024 in San Francisco San Francisco Temperature History January 2024 Hourly Temperature in January 2024 in San Francisco Hours of Daylight and Twilight in January 2024 in San Francisco80 deg, E Cloud Cover Mostly Cloudy 18,000 ft Mostly Clear 4,000 ft Partly Cloudy 15,000 ft Raw: KSFO 121956Z 08006KT 10SM FEW040 SCT150 BKN180 11/07 A3028 RMK AO2 SLP254 T01110067 This report shows the past weather for San Francisco, providing a weather history for January 2024.'}]\", name='tavily_search_results_json')]}\n", + "----\n" + ] + } + ], + "source": [ + "inputs = {\"messages\": [HumanMessage(content=\"what is the weather in sf? return this result directly by setting return_direct = True\")]}\n", + "for s in app.stream(inputs):\n", + " print(list(s.values())[0])\n", + " print(\"----\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2d2e89d8-0e35-465c-8984-1aa37a132b03", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.1" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/messages_executor_how_to/force-calling-a-tool-first.ipynb b/examples/messages_executor_how_to/force-calling-a-tool-first.ipynb new file mode 100644 index 000000000..d4fb92bf5 --- /dev/null +++ b/examples/messages_executor_how_to/force-calling-a-tool-first.ipynb @@ -0,0 +1,287 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "aa9f9110-ea74-43af-aa72-6b45518abd6e", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain import hub\n", + "from langchain_openai import ChatOpenAI\n", + "from langchain_community.tools.tavily_search import TavilySearchResults\n", + "from langgraph.prebuilt.messages_executor import create_messages_executor\n", + "from langchain_core.messages import HumanMessage" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "0f005b2d-ddc0-4d60-8af9-9eb3dbeb45b7", + "metadata": {}, + "outputs": [], + "source": [ + "tools = [TavilySearchResults(max_results=1)]\n", + "model = ChatOpenAI()" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "02831581-f1ba-4701-ba33-f0f68468907d", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain.tools.render import format_tool_to_openai_function" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "a5c0ca12-4922-461c-b740-62ff99f8ae56", + "metadata": {}, + "outputs": [], + "source": [ + "model = model.bind_functions([format_tool_to_openai_function(t) for t in tools])" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "57547622-ddd8-4179-aa4a-e1b69ca4523c", + "metadata": {}, + "outputs": [], + "source": [ + "from langgraph.prebuilt.tool_executor import ToolExecutor\n", + "tool_executor = ToolExecutor(tools)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "96141ebd-32af-4c9d-a0b0-f46482b8bb88", + "metadata": {}, + "outputs": [], + "source": [ + "from typing import TypedDict, Annotated, Sequence\n", + "import operator\n", + "from langchain_core.messages import BaseMessage\n", + "# We create the AgentState that we will pass around\n", + "# This simply involves a list of messages\n", + "# We want steps to return messages to append to the list\n", + "# So we annotate the messages attribute with operator.add\n", + "class AgentState(TypedDict):\n", + " messages: Annotated[Sequence[BaseMessage], operator.add]" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "f422dc99-672e-414f-88b3-1a6dd21b6882", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_core.agents import AgentAction\n", + "import json\n", + "from langchain_core.messages import FunctionMessage\n", + "\n", + "# Define the function that determines whether to continue or not\n", + "def should_continue(state):\n", + " messages = state['messages']\n", + " last_message = messages[-1]\n", + " # If there is no function call, then we finish\n", + " if \"function_call\" not in last_message.additional_kwargs:\n", + " return \"end\"\n", + " # Otherwise if there is, we continue\n", + " else:\n", + " return \"continue\"\n", + "\n", + "# Define the function that calls the model\n", + "def call_model(state):\n", + " messages = state['messages']\n", + " response = model.invoke(messages)\n", + " # We return a list, because this will get added to the existing list\n", + " return {\"messages\": [response]}\n", + "\n", + "# Define the function to execute tools\n", + "def call_tool(state):\n", + " messages = state['messages']\n", + " # Based on the continue condition\n", + " # we know the last message involves a function call\n", + " last_message = messages[-1]\n", + " # We construct an AgentAction from the function_call\n", + " action = AgentAction(\n", + " tool=last_message.additional_kwargs[\"function_call\"][\"name\"],\n", + " tool_input=json.loads(last_message.additional_kwargs[\"function_call\"][\"arguments\"]),\n", + " log=\"\",\n", + " )\n", + " # We call the tool_executor and get back a response\n", + " response = tool_executor.invoke(action)\n", + " # We use the response to create a FunctionMessage\n", + " function_message = FunctionMessage(content=str(response), name=action.tool)\n", + " # We return a list, because this will get added to the existing list\n", + " return {\"messages\": [function_message]}\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "47bf79f1-652c-43dc-aeb3-0f0de4400539", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'tavily_search_results_json'" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "tools[0].name" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "ab62da37-9632-4dc8-833b-feb4c62bab37", + "metadata": {}, + "outputs": [], + "source": [ + "# This is the new first - the first call of the model we want to explicitly hard-code some action\n", + "from langchain_core.messages import AIMessage\n", + "import json\n", + "\n", + "def first_model(state):\n", + " human_input = state['messages'][-1].content\n", + " return {\"messages\": [AIMessage(\n", + " content=\"\", \n", + " additional_kwargs={\n", + " \"function_call\": {\n", + " \"name\": \"tavily_search_results_json\", \n", + " \"arguments\": json.dumps({\"query\": human_input})\n", + " }\n", + " }\n", + " )\n", + " ]}" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "1133ec83-7af9-4444-9f88-c793fbdce214", + "metadata": {}, + "outputs": [], + "source": [ + "from langgraph.graph import StateGraph, END\n", + "# Define a new graph\n", + "workflow = StateGraph(AgentState)\n", + "\n", + "# Define the new entrypoint\n", + "workflow.add_node(\"first_agent\", first_model)\n", + "\n", + "# Define the two nodes we will cycle between\n", + "workflow.add_node(\"agent\", call_model)\n", + "workflow.add_node(\"action\", call_tool)\n", + "\n", + "# Set the entrypoint as `agent`\n", + "# This means that this node is the first one called\n", + "workflow.set_entry_point(\"first_agent\")\n", + "\n", + "# We now add a conditional edge\n", + "workflow.add_conditional_edges(\n", + " # First, we define the start node. We use `agent`.\n", + " # This means these are the edges taken after the `agent` node is called.\n", + " \"agent\",\n", + " # Next, we pass in the function that will determine which node is called next.\n", + " should_continue,\n", + " # Finally we pass in a mapping.\n", + " # The keys are strings, and the values are other nodes.\n", + " # END is a special node marking that the graph should finish.\n", + " # What will happen is we will call `should_continue`, and then the output of that\n", + " # will be matched against the keys in this mapping.\n", + " # Based on which one it matches, that node will then be called.\n", + " {\n", + " # If `tools`, then we call the tool node.\n", + " \"continue\": \"action\",\n", + " # Otherwise we finish.\n", + " \"end\": END\n", + " }\n", + ")\n", + "\n", + "# We now add a normal edge from `tools` to `agent`.\n", + "# This means that after `tools` is called, `agent` node is called next.\n", + "workflow.add_edge('action', 'agent')\n", + "\n", + "# After we call the first agent, we know we want to go to action\n", + "workflow.add_edge('first_agent', 'action')\n", + "\n", + "# Finally, we compile it!\n", + "# This compiles it into a LangChain Runnable,\n", + "# meaning you can use it as you would any other runnable\n", + "app = workflow.compile()" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "289c648f-bfc6-464f-8df9-50be8b1b9e48", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'messages': [AIMessage(content='', additional_kwargs={'function_call': {'name': 'tavily_search_results_json', 'arguments': '{\"query\": \"what is the weather in sf\"}'}})]}\n", + "----\n", + "{'messages': [FunctionMessage(content=\"[{'url': 'https://weatherspark.com/h/m/557/2024/1/Historical-Weather-in-January-2024-in-San-Francisco-California-United-States', 'content': 'January 2024 Weather History in San Francisco California, United States Daily Precipitation in January 2024 in San Francisco Observed Weather in January 2024 in San Francisco San Francisco Temperature History January 2024 Hourly Temperature in January 2024 in San Francisco Hours of Daylight and Twilight in January 2024 in San Francisco80 deg, E Cloud Cover Mostly Cloudy 18,000 ft Mostly Clear 4,000 ft Partly Cloudy 15,000 ft Raw: KSFO 121956Z 08006KT 10SM FEW040 SCT150 BKN180 11/07 A3028 RMK AO2 SLP254 T01110067 This report shows the past weather for San Francisco, providing a weather history for January 2024.'}]\", name='tavily_search_results_json')]}\n", + "----\n", + "{'messages': [AIMessage(content=\"I'm sorry, but I couldn't find the current weather in San Francisco. However, you can visit [this link](https://weatherspark.com/h/m/557/2024/1/Historical-Weather-in-January-2024-in-San-Francisco-California-United-States) to see the historical weather data for January 2024 in San Francisco.\")]}\n", + "----\n", + "{'messages': [HumanMessage(content='what is the weather in sf'), AIMessage(content='', additional_kwargs={'function_call': {'name': 'tavily_search_results_json', 'arguments': '{\"query\": \"what is the weather in sf\"}'}}), FunctionMessage(content=\"[{'url': 'https://weatherspark.com/h/m/557/2024/1/Historical-Weather-in-January-2024-in-San-Francisco-California-United-States', 'content': 'January 2024 Weather History in San Francisco California, United States Daily Precipitation in January 2024 in San Francisco Observed Weather in January 2024 in San Francisco San Francisco Temperature History January 2024 Hourly Temperature in January 2024 in San Francisco Hours of Daylight and Twilight in January 2024 in San Francisco80 deg, E Cloud Cover Mostly Cloudy 18,000 ft Mostly Clear 4,000 ft Partly Cloudy 15,000 ft Raw: KSFO 121956Z 08006KT 10SM FEW040 SCT150 BKN180 11/07 A3028 RMK AO2 SLP254 T01110067 This report shows the past weather for San Francisco, providing a weather history for January 2024.'}]\", name='tavily_search_results_json'), AIMessage(content=\"I'm sorry, but I couldn't find the current weather in San Francisco. However, you can visit [this link](https://weatherspark.com/h/m/557/2024/1/Historical-Weather-in-January-2024-in-San-Francisco-California-United-States) to see the historical weather data for January 2024 in San Francisco.\")]}\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": "4cd4b39f-831a-4818-bf56-99cf301b0555", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.1" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/messages_executor_how_to/human-in-the-loop.ipynb b/examples/messages_executor_how_to/human-in-the-loop.ipynb new file mode 100644 index 000000000..5f6219809 --- /dev/null +++ b/examples/messages_executor_how_to/human-in-the-loop.ipynb @@ -0,0 +1,252 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "aa9f9110-ea74-43af-aa72-6b45518abd6e", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain import hub\n", + "from langchain_openai import ChatOpenAI\n", + "from langchain_community.tools.tavily_search import TavilySearchResults\n", + "from langgraph.prebuilt.messages_executor import create_messages_executor\n", + "from langchain_core.messages import HumanMessage" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "0f005b2d-ddc0-4d60-8af9-9eb3dbeb45b7", + "metadata": {}, + "outputs": [], + "source": [ + "tools = [TavilySearchResults(max_results=1)]\n", + "model = ChatOpenAI()" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "02831581-f1ba-4701-ba33-f0f68468907d", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain.tools.render import format_tool_to_openai_function" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "a5c0ca12-4922-461c-b740-62ff99f8ae56", + "metadata": {}, + "outputs": [], + "source": [ + "model = model.bind_functions([format_tool_to_openai_function(t) for t in tools])" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "57547622-ddd8-4179-aa4a-e1b69ca4523c", + "metadata": {}, + "outputs": [], + "source": [ + "from langgraph.prebuilt.tool_executor import ToolExecutor\n", + "tool_executor = ToolExecutor(tools)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "96141ebd-32af-4c9d-a0b0-f46482b8bb88", + "metadata": {}, + "outputs": [], + "source": [ + "from typing import TypedDict, Annotated, Sequence\n", + "import operator\n", + "from langchain_core.messages import BaseMessage\n", + "# We create the AgentState that we will pass around\n", + "# This simply involves a list of messages\n", + "# We want steps to return messages to append to the list\n", + "# So we annotate the messages attribute with operator.add\n", + "class AgentState(TypedDict):\n", + " messages: Annotated[Sequence[BaseMessage], operator.add]" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "f422dc99-672e-414f-88b3-1a6dd21b6882", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_core.agents import AgentAction\n", + "import json\n", + "from langchain_core.messages import FunctionMessage\n", + "\n", + "# Define the function that determines whether to continue or not\n", + "def should_continue(state):\n", + " messages = state['messages']\n", + " last_message = messages[-1]\n", + " # If there is no function call, then we finish\n", + " if \"function_call\" not in last_message.additional_kwargs:\n", + " return \"end\"\n", + " # Otherwise if there is, we continue\n", + " else:\n", + " return \"continue\"\n", + "\n", + "# Define the function that calls the model\n", + "def call_model(state):\n", + " messages = state['messages']\n", + " response = model.invoke(messages)\n", + " # We return a list, because this will get added to the existing list\n", + " return {\"messages\": [response]}\n", + "\n", + "# Define the function to execute tools\n", + "# Here we add some lines to add a human-in-the-loop\n", + "def call_tool(state):\n", + " messages = state['messages']\n", + " # Based on the continue condition\n", + " # we know the last message involves a function call\n", + " last_message = messages[-1]\n", + " # We construct an AgentAction from the function_call\n", + " action = AgentAction(\n", + " tool=last_message.additional_kwargs[\"function_call\"][\"name\"],\n", + " tool_input=json.loads(last_message.additional_kwargs[\"function_call\"][\"arguments\"]),\n", + " log=\"\",\n", + " )\n", + " response = input(prompt=f\"[y/n] continue with: {action}?\")\n", + " if response == \"n\":\n", + " raise ValueError\n", + " # We call the tool_executor and get back a response\n", + " response = tool_executor.invoke(action)\n", + " # We use the response to create a FunctionMessage\n", + " function_message = FunctionMessage(content=str(response), name=action.tool)\n", + " # We return a list, because this will get added to the existing list\n", + " return {\"messages\": [function_message]}\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "1133ec83-7af9-4444-9f88-c793fbdce214", + "metadata": {}, + "outputs": [], + "source": [ + "from langgraph.graph import StateGraph, END\n", + "# Define a new graph\n", + "workflow = StateGraph(AgentState)\n", + "\n", + "# Define the two nodes we will cycle between\n", + "workflow.add_node(\"agent\", call_model)\n", + "workflow.add_node(\"action\", call_tool)\n", + "\n", + "# Set the entrypoint as `agent`\n", + "# This means that this node is the first one called\n", + "workflow.set_entry_point(\"agent\")\n", + "\n", + "# We now add a conditional edge\n", + "workflow.add_conditional_edges(\n", + " # First, we define the start node. We use `agent`.\n", + " # This means these are the edges taken after the `agent` node is called.\n", + " \"agent\",\n", + " # Next, we pass in the function that will determine which node is called next.\n", + " should_continue,\n", + " # Finally we pass in a mapping.\n", + " # The keys are strings, and the values are other nodes.\n", + " # END is a special node marking that the graph should finish.\n", + " # What will happen is we will call `should_continue`, and then the output of that\n", + " # will be matched against the keys in this mapping.\n", + " # Based on which one it matches, that node will then be called.\n", + " {\n", + " # If `tools`, then we call the tool node.\n", + " \"continue\": \"action\",\n", + " # Otherwise we finish.\n", + " \"end\": END\n", + " }\n", + ")\n", + "\n", + "# We now add a normal edge from `tools` to `agent`.\n", + "# This means that after `tools` is called, `agent` node is called next.\n", + "workflow.add_edge('action', 'agent')\n", + "\n", + "# Finally, we compile it!\n", + "# This compiles it into a LangChain Runnable,\n", + "# meaning you can use it as you would any other runnable\n", + "app = workflow.compile()" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "289c648f-bfc6-464f-8df9-50be8b1b9e48", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'messages': [AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\\n \"query\": \"weather in San Francisco\"\\n}', 'name': 'tavily_search_results_json'}})]}\n", + "----\n" + ] + }, + { + "name": "stdin", + "output_type": "stream", + "text": [ + "[y/n] continue with: tool='tavily_search_results_json' tool_input={'query': 'weather in San Francisco'} log=''? y\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'messages': [FunctionMessage(content=\"[{'url': 'https://weatherspark.com/h/m/557/2024/1/Historical-Weather-in-January-2024-in-San-Francisco-California-United-States', 'content': 'January 2024 Weather History in San Francisco California, United States Daily Precipitation in January 2024 in San Francisco Observed Weather in January 2024 in San Francisco San Francisco Temperature History January 2024 Hourly Temperature in January 2024 in San Francisco Hours of Daylight and Twilight in January 2024 in San Francisco80 deg, E Cloud Cover Mostly Cloudy 18,000 ft Mostly Clear 4,000 ft Partly Cloudy 15,000 ft Raw: KSFO 121956Z 08006KT 10SM FEW040 SCT150 BKN180 11/07 A3028 RMK AO2 SLP254 T01110067 This report shows the past weather for San Francisco, providing a weather history for January 2024.'}]\", name='tavily_search_results_json')]}\n", + "----\n", + "{'messages': [AIMessage(content='The weather in San Francisco is currently not available. However, you can check the historical weather data for San Francisco in January 2024 [here](https://weatherspark.com/h/m/557/2024/1/Historical-Weather-in-January-2024-in-San-Francisco-California-United-States).')]}\n", + "----\n", + "{'messages': [HumanMessage(content='what is the weather in sf'), AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\\n \"query\": \"weather in San Francisco\"\\n}', 'name': 'tavily_search_results_json'}}), FunctionMessage(content=\"[{'url': 'https://weatherspark.com/h/m/557/2024/1/Historical-Weather-in-January-2024-in-San-Francisco-California-United-States', 'content': 'January 2024 Weather History in San Francisco California, United States Daily Precipitation in January 2024 in San Francisco Observed Weather in January 2024 in San Francisco San Francisco Temperature History January 2024 Hourly Temperature in January 2024 in San Francisco Hours of Daylight and Twilight in January 2024 in San Francisco80 deg, E Cloud Cover Mostly Cloudy 18,000 ft Mostly Clear 4,000 ft Partly Cloudy 15,000 ft Raw: KSFO 121956Z 08006KT 10SM FEW040 SCT150 BKN180 11/07 A3028 RMK AO2 SLP254 T01110067 This report shows the past weather for San Francisco, providing a weather history for January 2024.'}]\", name='tavily_search_results_json'), AIMessage(content='The weather in San Francisco is currently not available. However, you can check the historical weather data for San Francisco in January 2024 [here](https://weatherspark.com/h/m/557/2024/1/Historical-Weather-in-January-2024-in-San-Francisco-California-United-States).')]}\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": "4cd4b39f-831a-4818-bf56-99cf301b0555", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.1" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/messages_executor_how_to/managing-agent-steps.ipynb b/examples/messages_executor_how_to/managing-agent-steps.ipynb new file mode 100644 index 000000000..b136431f5 --- /dev/null +++ b/examples/messages_executor_how_to/managing-agent-steps.ipynb @@ -0,0 +1,237 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "aa9f9110-ea74-43af-aa72-6b45518abd6e", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain import hub\n", + "from langchain_openai import ChatOpenAI\n", + "from langchain_community.tools.tavily_search import TavilySearchResults\n", + "from langgraph.prebuilt.messages_executor import create_messages_executor\n", + "from langchain_core.messages import HumanMessage" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "0f005b2d-ddc0-4d60-8af9-9eb3dbeb45b7", + "metadata": {}, + "outputs": [], + "source": [ + "tools = [TavilySearchResults(max_results=1)]\n", + "model = ChatOpenAI()" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "02831581-f1ba-4701-ba33-f0f68468907d", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain.tools.render import format_tool_to_openai_function" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "a5c0ca12-4922-461c-b740-62ff99f8ae56", + "metadata": {}, + "outputs": [], + "source": [ + "model = model.bind_functions([format_tool_to_openai_function(t) for t in tools])" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "57547622-ddd8-4179-aa4a-e1b69ca4523c", + "metadata": {}, + "outputs": [], + "source": [ + "from langgraph.prebuilt.tool_executor import ToolExecutor\n", + "tool_executor = ToolExecutor(tools)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "96141ebd-32af-4c9d-a0b0-f46482b8bb88", + "metadata": {}, + "outputs": [], + "source": [ + "from typing import TypedDict, Annotated, Sequence\n", + "import operator\n", + "from langchain_core.messages import BaseMessage\n", + "# We create the AgentState that we will pass around\n", + "# This simply involves a list of messages\n", + "# We want steps to return messages to append to the list\n", + "# So we annotate the messages attribute with operator.add\n", + "class AgentState(TypedDict):\n", + " messages: Annotated[Sequence[BaseMessage], operator.add]" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "f422dc99-672e-414f-88b3-1a6dd21b6882", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_core.agents import AgentAction\n", + "import json\n", + "from langchain_core.messages import FunctionMessage\n", + "\n", + "# Define the function that determines whether to continue or not\n", + "def should_continue(state):\n", + " messages = state['messages']\n", + " last_message = messages[-1]\n", + " # If there is no function call, then we finish\n", + " if \"function_call\" not in last_message.additional_kwargs:\n", + " return \"end\"\n", + " # Otherwise if there is, we continue\n", + " else:\n", + " return \"continue\"\n", + "\n", + "# Define the function that calls the model\n", + "def call_model(state):\n", + " messages = state['messages']\n", + " if len(messages) > 10:\n", + " messages = messages[-10:]\n", + " response = model.invoke(messages)\n", + " # We return a list, because this will get added to the existing list\n", + " return {\"messages\": [response]}\n", + "\n", + "# Define the function to execute tools\n", + "def call_tool(state):\n", + " messages = state['messages']\n", + " # Based on the continue condition\n", + " # we know the last message involves a function call\n", + " last_message = messages[-1]\n", + " # We construct an AgentAction from the function_call\n", + " action = AgentAction(\n", + " tool=last_message.additional_kwargs[\"function_call\"][\"name\"],\n", + " tool_input=json.loads(last_message.additional_kwargs[\"function_call\"][\"arguments\"]),\n", + " log=\"\",\n", + " )\n", + " # We call the tool_executor and get back a response\n", + " response = tool_executor.invoke(action)\n", + " # We use the response to create a FunctionMessage\n", + " function_message = FunctionMessage(content=str(response), name=action.tool)\n", + " # We return a list, because this will get added to the existing list\n", + " return {\"messages\": [function_message]}\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "1133ec83-7af9-4444-9f88-c793fbdce214", + "metadata": {}, + "outputs": [], + "source": [ + "from langgraph.graph import StateGraph, END\n", + "# Define a new graph\n", + "workflow = StateGraph(AgentState)\n", + "\n", + "# Define the two nodes we will cycle between\n", + "workflow.add_node(\"agent\", call_model)\n", + "workflow.add_node(\"action\", call_tool)\n", + "\n", + "# Set the entrypoint as `agent`\n", + "# This means that this node is the first one called\n", + "workflow.set_entry_point(\"agent\")\n", + "\n", + "# We now add a conditional edge\n", + "workflow.add_conditional_edges(\n", + " # First, we define the start node. We use `agent`.\n", + " # This means these are the edges taken after the `agent` node is called.\n", + " \"agent\",\n", + " # Next, we pass in the function that will determine which node is called next.\n", + " should_continue,\n", + " # Finally we pass in a mapping.\n", + " # The keys are strings, and the values are other nodes.\n", + " # END is a special node marking that the graph should finish.\n", + " # What will happen is we will call `should_continue`, and then the output of that\n", + " # will be matched against the keys in this mapping.\n", + " # Based on which one it matches, that node will then be called.\n", + " {\n", + " # If `tools`, then we call the tool node.\n", + " \"continue\": \"action\",\n", + " # Otherwise we finish.\n", + " \"end\": END\n", + " }\n", + ")\n", + "\n", + "# We now add a normal edge from `tools` to `agent`.\n", + "# This means that after `tools` is called, `agent` node is called next.\n", + "workflow.add_edge('action', 'agent')\n", + "\n", + "# Finally, we compile it!\n", + "# This compiles it into a LangChain Runnable,\n", + "# meaning you can use it as you would any other runnable\n", + "app = workflow.compile()" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "289c648f-bfc6-464f-8df9-50be8b1b9e48", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'messages': [AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\\n \"query\": \"weather in San Francisco\"\\n}', 'name': 'tavily_search_results_json'}})]}\n", + "----\n", + "{'messages': [FunctionMessage(content=\"[{'url': 'https://weatherspark.com/h/m/557/2024/1/Historical-Weather-in-January-2024-in-San-Francisco-California-United-States', 'content': 'January 2024 Weather History in San Francisco California, United States Daily Precipitation in January 2024 in San Francisco Observed Weather in January 2024 in San Francisco San Francisco Temperature History January 2024 Hourly Temperature in January 2024 in San Francisco Hours of Daylight and Twilight in January 2024 in San Francisco80 deg, E Cloud Cover Mostly Cloudy 18,000 ft Mostly Clear 4,000 ft Partly Cloudy 15,000 ft Raw: KSFO 121956Z 08006KT 10SM FEW040 SCT150 BKN180 11/07 A3028 RMK AO2 SLP254 T01110067 This report shows the past weather for San Francisco, providing a weather history for January 2024.'}]\", name='tavily_search_results_json')]}\n", + "----\n", + "{'messages': [AIMessage(content='The weather in San Francisco is currently not available. However, you can check the weather history for January 2024 in San Francisco on [weatherspark.com](https://weatherspark.com/h/m/557/2024/1/Historical-Weather-in-January-2024-in-San-Francisco-California-United-States).')]}\n", + "----\n", + "{'messages': [HumanMessage(content='what is the weather in sf'), AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\\n \"query\": \"weather in San Francisco\"\\n}', 'name': 'tavily_search_results_json'}}), FunctionMessage(content=\"[{'url': 'https://weatherspark.com/h/m/557/2024/1/Historical-Weather-in-January-2024-in-San-Francisco-California-United-States', 'content': 'January 2024 Weather History in San Francisco California, United States Daily Precipitation in January 2024 in San Francisco Observed Weather in January 2024 in San Francisco San Francisco Temperature History January 2024 Hourly Temperature in January 2024 in San Francisco Hours of Daylight and Twilight in January 2024 in San Francisco80 deg, E Cloud Cover Mostly Cloudy 18,000 ft Mostly Clear 4,000 ft Partly Cloudy 15,000 ft Raw: KSFO 121956Z 08006KT 10SM FEW040 SCT150 BKN180 11/07 A3028 RMK AO2 SLP254 T01110067 This report shows the past weather for San Francisco, providing a weather history for January 2024.'}]\", name='tavily_search_results_json'), AIMessage(content='The weather in San Francisco is currently not available. However, you can check the weather history for January 2024 in San Francisco on [weatherspark.com](https://weatherspark.com/h/m/557/2024/1/Historical-Weather-in-January-2024-in-San-Francisco-California-United-States).')]}\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": "4cd4b39f-831a-4818-bf56-99cf301b0555", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.1" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/messages_executor_how_to/respond-in-format.ipynb b/examples/messages_executor_how_to/respond-in-format.ipynb new file mode 100644 index 000000000..85085b8e1 --- /dev/null +++ b/examples/messages_executor_how_to/respond-in-format.ipynb @@ -0,0 +1,265 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "aa9f9110-ea74-43af-aa72-6b45518abd6e", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain import hub\n", + "from langchain_openai import ChatOpenAI\n", + "from langchain_community.tools.tavily_search import TavilySearchResults\n", + "from langgraph.prebuilt.messages_executor import create_messages_executor\n", + "from langchain_core.messages import HumanMessage" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "0f005b2d-ddc0-4d60-8af9-9eb3dbeb45b7", + "metadata": {}, + "outputs": [], + "source": [ + "tools = [TavilySearchResults(max_results=1)]\n", + "model = ChatOpenAI()" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "02831581-f1ba-4701-ba33-f0f68468907d", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain.tools.render import format_tool_to_openai_function" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "a3bdf328-f34c-421e-9771-85f2740fbad6", + "metadata": {}, + "outputs": [], + "source": [ + "# Here we bind an additional function beside just tools - the response format\n", + "functions = [format_tool_to_openai_function(t) for t in tools]\n", + "\n", + "from langchain_core.pydantic_v1 import BaseModel, Field\n", + "\n", + "class Response(BaseModel):\n", + " \"\"\"Final response to the user\"\"\"\n", + " temperature: float = Field(description=\"the temperature\")\n", + " other_notes: str = Field(description=\"any other notes about the weather\")\n", + "\n", + "from langchain_core.utils.function_calling import convert_pydantic_to_openai_function" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "a5c0ca12-4922-461c-b740-62ff99f8ae56", + "metadata": {}, + "outputs": [], + "source": [ + "model = model.bind_functions(functions + [convert_pydantic_to_openai_function(Response)])" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "57547622-ddd8-4179-aa4a-e1b69ca4523c", + "metadata": {}, + "outputs": [], + "source": [ + "from langgraph.prebuilt.tool_executor import ToolExecutor\n", + "tool_executor = ToolExecutor(tools)" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "96141ebd-32af-4c9d-a0b0-f46482b8bb88", + "metadata": {}, + "outputs": [], + "source": [ + "from typing import TypedDict, Annotated, Sequence\n", + "import operator\n", + "from langchain_core.messages import BaseMessage\n", + "# We create the AgentState that we will pass around\n", + "# This simply involves a list of messages\n", + "# We want steps to return messages to append to the list\n", + "# So we annotate the messages attribute with operator.add\n", + "class AgentState(TypedDict):\n", + " messages: Annotated[Sequence[BaseMessage], operator.add]" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "0759bddc-010a-4e3f-9f41-3a51c1ea5144", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_core.agents import AgentAction\n", + "import json\n", + "from langchain_core.messages import FunctionMessage\n", + "\n", + "# Define the function that determines whether to continue or not\n", + "# This needs to get updated\n", + "def should_continue(state):\n", + " messages = state['messages']\n", + " last_message = messages[-1]\n", + " # If there is no function call, then we finish\n", + " if \"function_call\" not in last_message.additional_kwargs:\n", + " return \"end\"\n", + " # Otherwise if there is, we need to check what type of function call it is\n", + " elif last_message.additional_kwargs[\"function_call\"][\"name\"] == \"Response\":\n", + " return \"end\"\n", + " else:\n", + " return \"continue\"" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "2a048e72-525a-4dcb-a91d-efacaddd8848", + "metadata": {}, + "outputs": [], + "source": [ + "# Define the function that calls the model\n", + "def call_model(state):\n", + " messages = state['messages']\n", + " response = model.invoke(messages)\n", + " # We return a list, because this will get added to the existing list\n", + " return {\"messages\": [response]}\n", + "\n", + "# Define the function to execute tools\n", + "def call_tool(state):\n", + " messages = state['messages']\n", + " # Based on the continue condition\n", + " # we know the last message involves a function call\n", + " last_message = messages[-1]\n", + " # We construct an AgentAction from the function_call\n", + " action = AgentAction(\n", + " tool=last_message.additional_kwargs[\"function_call\"][\"name\"],\n", + " tool_input=json.loads(last_message.additional_kwargs[\"function_call\"][\"arguments\"]),\n", + " log=\"\",\n", + " )\n", + " # We call the tool_executor and get back a response\n", + " response = tool_executor.invoke(action)\n", + " # We use the response to create a FunctionMessage\n", + " function_message = FunctionMessage(content=str(response), name=action.tool)\n", + " # We return a list, because this will get added to the existing list\n", + " return {\"messages\": [function_message]}" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "1133ec83-7af9-4444-9f88-c793fbdce214", + "metadata": {}, + "outputs": [], + "source": [ + "from langgraph.graph import StateGraph, END\n", + "# Define a new graph\n", + "workflow = StateGraph(AgentState)\n", + "\n", + "# Define the two nodes we will cycle between\n", + "workflow.add_node(\"agent\", call_model)\n", + "workflow.add_node(\"action\", call_tool)\n", + "\n", + "# Set the entrypoint as `agent`\n", + "# This means that this node is the first one called\n", + "workflow.set_entry_point(\"agent\")\n", + "\n", + "# We now add a conditional edge\n", + "workflow.add_conditional_edges(\n", + " # First, we define the start node. We use `agent`.\n", + " # This means these are the edges taken after the `agent` node is called.\n", + " \"agent\",\n", + " # Next, we pass in the function that will determine which node is called next.\n", + " should_continue,\n", + " # Finally we pass in a mapping.\n", + " # The keys are strings, and the values are other nodes.\n", + " # END is a special node marking that the graph should finish.\n", + " # What will happen is we will call `should_continue`, and then the output of that\n", + " # will be matched against the keys in this mapping.\n", + " # Based on which one it matches, that node will then be called.\n", + " {\n", + " # If `tools`, then we call the tool node.\n", + " \"continue\": \"action\",\n", + " # Otherwise we finish.\n", + " \"end\": END\n", + " }\n", + ")\n", + "\n", + "# We now add a normal edge from `tools` to `agent`.\n", + "# This means that after `tools` is called, `agent` node is called next.\n", + "workflow.add_edge('action', 'agent')\n", + "\n", + "# Finally, we compile it!\n", + "# This compiles it into a LangChain Runnable,\n", + "# meaning you can use it as you would any other runnable\n", + "app = workflow.compile()" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "289c648f-bfc6-464f-8df9-50be8b1b9e48", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'messages': [AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\\n \"query\": \"weather in San Francisco\"\\n}', 'name': 'tavily_search_results_json'}})]}\n", + "----\n", + "{'messages': [FunctionMessage(content=\"[{'url': 'https://weatherspark.com/h/m/557/2024/1/Historical-Weather-in-January-2024-in-San-Francisco-California-United-States', 'content': 'January 2024 Weather History in San Francisco California, United States Daily Precipitation in January 2024 in San Francisco Observed Weather in January 2024 in San Francisco San Francisco Temperature History January 2024 Hourly Temperature in January 2024 in San Francisco Hours of Daylight and Twilight in January 2024 in San Francisco80 deg, E Cloud Cover Mostly Cloudy 18,000 ft Mostly Clear 4,000 ft Partly Cloudy 15,000 ft Raw: KSFO 121956Z 08006KT 10SM FEW040 SCT150 BKN180 11/07 A3028 RMK AO2 SLP254 T01110067 This report shows the past weather for San Francisco, providing a weather history for January 2024.'}]\", name='tavily_search_results_json')]}\n", + "----\n", + "{'messages': [AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\\n \"temperature\": 80,\\n \"other_notes\": \"Mostly cloudy\"\\n}', 'name': 'Response'}})]}\n", + "----\n", + "{'messages': [HumanMessage(content='what is the weather in sf'), AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\\n \"query\": \"weather in San Francisco\"\\n}', 'name': 'tavily_search_results_json'}}), FunctionMessage(content=\"[{'url': 'https://weatherspark.com/h/m/557/2024/1/Historical-Weather-in-January-2024-in-San-Francisco-California-United-States', 'content': 'January 2024 Weather History in San Francisco California, United States Daily Precipitation in January 2024 in San Francisco Observed Weather in January 2024 in San Francisco San Francisco Temperature History January 2024 Hourly Temperature in January 2024 in San Francisco Hours of Daylight and Twilight in January 2024 in San Francisco80 deg, E Cloud Cover Mostly Cloudy 18,000 ft Mostly Clear 4,000 ft Partly Cloudy 15,000 ft Raw: KSFO 121956Z 08006KT 10SM FEW040 SCT150 BKN180 11/07 A3028 RMK AO2 SLP254 T01110067 This report shows the past weather for San Francisco, providing a weather history for January 2024.'}]\", name='tavily_search_results_json'), AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\\n \"temperature\": 80,\\n \"other_notes\": \"Mostly cloudy\"\\n}', 'name': 'Response'}})]}\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": "4cd4b39f-831a-4818-bf56-99cf301b0555", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.1" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/langgraph/prebuilt/__init__.py b/langgraph/prebuilt/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/langgraph/prebuilt/agent_executor.py b/langgraph/prebuilt/agent_executor.py new file mode 100644 index 000000000..77b365e35 --- /dev/null +++ b/langgraph/prebuilt/agent_executor.py @@ -0,0 +1,90 @@ +from typing import Annotated, TypedDict +import operator +from langchain_core.agents import AgentAction, AgentFinish +from langgraph.graph import StateGraph, END +from langgraph.prebuilt.tool_executor import ToolExecutor + + + + +def create_agent_executor(agent_runnable, tools, input_schema=None): + + if isinstance(tools, ToolExecutor): + tool_executor = tools + else: + tool_executor = ToolExecutor(tools) + + + if input_schema is None: + class AgentState(TypedDict): + input: str + agent_outcome: AgentAction | AgentFinish | None + intermediate_steps: Annotated[list[tuple[AgentAction, str]], operator.add] + + else: + class AgentState(input_schema): + agent_outcome: AgentAction | AgentFinish | None + intermediate_steps: Annotated[list[tuple[AgentAction, str]], operator.add] + + def should_continue(data): + # If the agent outcome is an AgentFinish, then we return `exit` string + # This will be used when setting up the graph to define the flow + if isinstance(data['agent_outcome'], AgentFinish): + return "end" + # Otherwise, an AgentAction is returned + # Here we return `continue` string + # This will be used when setting up the graph to define the flow + else: + return "continue" + + def run_agent(data): + agent_outcome = agent_runnable.invoke(data) + return {"agent_outcome": agent_outcome} + + # Define the function to execute tools + def execute_tools(data): + # Get the most recent agent_outcome - this is the key added in the `agent` above + agent_action = data['agent_outcome'] + output = tool_executor.invoke(agent_action) + return {"intermediate_steps": [(agent_action, str(output))]} + + # Define a new graph + workflow = StateGraph(AgentState) + + # Define the two nodes we will cycle between + workflow.add_node("agent", run_agent) + workflow.add_node("action", execute_tools) + + # Set the entrypoint as `agent` + # This means that this node is the first one called + workflow.set_entry_point("agent") + + # We now add a conditional edge + workflow.add_conditional_edges( + # First, we define the start node. We use `agent`. + # This means these are the edges taken after the `agent` node is called. + "agent", + # Next, we pass in the function that will determine which node is called next. + should_continue, + # Finally we pass in a mapping. + # The keys are strings, and the values are other nodes. + # END is a special node marking that the graph should finish. + # What will happen is we will call `should_continue`, and then the output of that + # will be matched against the keys in this mapping. + # Based on which one it matches, that node will then be called. + { + # If `tools`, then we call the tool node. + "continue": "action", + # Otherwise we finish. + "end": END + } + ) + + # We now add a normal edge from `tools` to `agent`. + # This means that after `tools` is called, `agent` node is called next. + workflow.add_edge('action', 'agent') + + # Finally, we compile it! + # This compiles it into a LangChain Runnable, + # meaning you can use it as you would any other runnable + return workflow.compile() diff --git a/langgraph/prebuilt/messages_executor.py b/langgraph/prebuilt/messages_executor.py new file mode 100644 index 000000000..760f20ab1 --- /dev/null +++ b/langgraph/prebuilt/messages_executor.py @@ -0,0 +1,122 @@ +from langchain_core.runnables import RunnablePassthrough +from langchain_core.messages import FunctionMessage +from langchain_core.agents import AgentFinish, AgentAction +import json + +from langchain.tools.render import format_tool_to_openai_function +from langgraph.prebuilt.tool_executor import ToolExecutor +from langchain_core.utils.function_calling import convert_pydantic_to_openai_function +from typing import Annotated, TypedDict, Sequence +from langchain_core.messages import BaseMessage +import operator +from langchain_core.agents import AgentAction, AgentFinish +from langgraph.graph import StateGraph, END + + +def _get_tool_executor_and_functions(tools, response_format): + if isinstance(tools, ToolExecutor): + tool_executor = tools + tool_classes = tools.tools + else: + tool_executor = ToolExecutor(tools) + tool_classes = tools + + functions = [format_tool_to_openai_function(t) for t in tool_classes] + if response_format is not None: + functions.append(convert_pydantic_to_openai_function(response_format)) + return tool_executor, functions + + +def create_messages_executor(model, tools, response_format = None): + tool_executor, functions = _get_tool_executor_and_functions(tools, response_format) + model = model.bind_functions([format_tool_to_openai_function(t) for t in tools]) + + # Define the function that determines whether to continue or not + def should_continue(state): + messages = state['messages'] + last_message = messages[-1] + # If there is no function call, then we finish + if "function_call" not in last_message.additional_kwargs: + return "end" + # Otherwise if there is, we need to check what type of function call it is + else: + if response_format is None: + return "continue" + elif last_message.additional_kwargs["function_call"]["name"] == response_format.__name__: + return "end" + else: + return "continue" + + # Define the function that calls the model + def call_model(state): + messages = state['messages'] + response = model.invoke(messages) + # We return a list, because this will get added to the existing list + return {"messages": [response]} + + # Define the function to execute tools + def call_tool(state): + messages = state['messages'] + # Based on the continue condition + # we know the last message involves a function call + last_message = messages[-1] + # We construct an AgentAction from the function_call + action = AgentAction( + tool=last_message.additional_kwargs["function_call"]["name"], + tool_input=json.loads(last_message.additional_kwargs["function_call"]["arguments"]), + log="", + ) + # We call the tool_executor and get back a response + response = tool_executor.invoke(action) + # We use the response to create a FunctionMessage + function_message = FunctionMessage(content=str(response), name=action.tool) + # We return a list, because this will get added to the existing list + return {"messages": [function_message]} + + # We create the AgentState that we will pass around + # This simply involves a list of messages + # We want steps to return messages to append to the list + # So we annotate the messages attribute with operator.add + class AgentState(TypedDict): + messages: Annotated[Sequence[BaseMessage], operator.add] + + # Define a new graph + workflow = StateGraph(AgentState) + + # Define the two nodes we will cycle between + workflow.add_node("agent", call_model) + workflow.add_node("action", call_tool) + + # Set the entrypoint as `agent` + # This means that this node is the first one called + workflow.set_entry_point("agent") + + # We now add a conditional edge + workflow.add_conditional_edges( + # First, we define the start node. We use `agent`. + # This means these are the edges taken after the `agent` node is called. + "agent", + # Next, we pass in the function that will determine which node is called next. + should_continue, + # Finally we pass in a mapping. + # The keys are strings, and the values are other nodes. + # END is a special node marking that the graph should finish. + # What will happen is we will call `should_continue`, and then the output of that + # will be matched against the keys in this mapping. + # Based on which one it matches, that node will then be called. + { + # If `tools`, then we call the tool node. + "continue": "action", + # Otherwise we finish. + "end": END + } + ) + + # We now add a normal edge from `tools` to `agent`. + # This means that after `tools` is called, `agent` node is called next. + workflow.add_edge('action', 'agent') + + # Finally, we compile it! + # This compiles it into a LangChain Runnable, + # meaning you can use it as you would any other runnable + return workflow.compile() diff --git a/langgraph/prebuilt/tool_executor.py b/langgraph/prebuilt/tool_executor.py new file mode 100644 index 000000000..7f860f256 --- /dev/null +++ b/langgraph/prebuilt/tool_executor.py @@ -0,0 +1,40 @@ +from langchain_core.runnables import RunnableBinding, RunnableLambda +from typing import Sequence, Any +from langchain_core.tools import BaseTool +from langchain_core.agents import AgentAction +INVALID_TOOL_MSG_TEMPLATE = ( + "{requested_tool_name} is not a valid tool, " + "try one of [{available_tool_names_str}]." +) + +class ToolExecutor(RunnableBinding): + + tools: Sequence[BaseTool] + tool_map: dict + invalid_tool_msg_template: str + def __init__(self, tools: Sequence[BaseTool], invalid_tool_msg_template: str = INVALID_TOOL_MSG_TEMPLATE, **kwargs: Any) -> None: + + bound = RunnableLambda(self._execute, afunc=self._aexecute) + super().__init__(bound=bound, tools=tools, tool_map ={t.name: t for t in tools}, invalid_tool_msg_template=invalid_tool_msg_template, **kwargs) + + def _execute(self, tool_invocation: AgentAction) -> Any: + if tool_invocation.tool not in self.tool_map: + return self.invalid_tool_msg_template.format( + requested_tool_name=tool_invocation.tool, + available_tool_names_str=", ".join([t.name for t in self.tools]) + ) + else: + tool = self.tool_map[tool_invocation.tool] + output = tool.invoke(tool_invocation.tool_input) + return output + + async def _aexecute(self, tool_invocation: AgentAction) -> Any: + if tool_invocation.tool not in self.tool_map: + return self.invalid_tool_msg_template.format( + requested_tool_name=tool_invocation.tool, + available_tool_names_str=", ".join([t.name for t in self.tools]) + ) + else: + tool = self.tool_map[tool_invocation.tool] + output = await tool.ainvoke(tool_invocation.tool_input) + return output \ No newline at end of file