mirror of
https://github.com/langchain-ai/langgraph.git
synced 2026-09-11 12:17:53 +02:00
416 lines
20 KiB
Plaintext
416 lines
20 KiB
Plaintext
{
|
|
"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.chat_models import ChatOpenAI\n",
|
|
"from langchain_core.prompts import PromptTemplate\n",
|
|
"from langchain import hub\n",
|
|
"from langchain.agents import AgentExecutor, create_openai_functions_agent\n",
|
|
"from langchain_community.chat_models import ChatOpenAI\n",
|
|
"from langchain_community.tools.tavily_search import TavilySearchResults\n",
|
|
"from langchain_core.runnables import RunnablePassthrough, RunnableLambda\n",
|
|
"from permchain.langgraph import Actor, Graph, End\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",
|
|
"llm_agent = Actor(\"agent\", agent)\n",
|
|
"tool_actor = Actor(\"tools\", RunnableLambda(execute_tools))\n",
|
|
"end = End()\n",
|
|
"\n",
|
|
"workflow.add_node(llm_agent)\n",
|
|
"workflow.add_node(tool_actor)\n",
|
|
"\n",
|
|
"workflow.set_entry_point(llm_agent.key)\n",
|
|
"\n",
|
|
"workflow.add_conditional_edges(\n",
|
|
" llm_agent.key,\n",
|
|
" should_continue,\n",
|
|
" {\n",
|
|
" \"continue\": tool_actor.key,\n",
|
|
" \"exit\": end.key\n",
|
|
" }\n",
|
|
")\n",
|
|
"workflow.add_edge(tool_actor.key, llm_agent.key)\n",
|
|
"chain = workflow.compile()"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 2,
|
|
"id": "c46bd262-9605-4449-9391-f6b6e0fe440e",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stderr",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Retrying langchain_community.chat_models.openai.ChatOpenAI.completion_with_retry.<locals>._completion_with_retry in 4.0 seconds as it raised ServiceUnavailableError: The server is overloaded or not ready yet..\n",
|
|
"Retrying langchain_community.chat_models.openai.ChatOpenAI.completion_with_retry.<locals>._completion_with_retry in 4.0 seconds as it raised ServiceUnavailableError: The server is overloaded or not ready yet..\n"
|
|
]
|
|
},
|
|
{
|
|
"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.cbsnews.com/sanfrancisco/news/california-begins-2024-with-below-normal-snowpack-a-year-after-one-of-the-best-starts-in-decades/',\n",
|
|
" 'content': 'January 2, 2024 / 3:27 PM PST / AP More from CBS News First published on January 2, 2024 / 2:28 PM PST Watch CBS News California begins 2024 with below-normal snowpack a year after one of the best starts in decades between January and April.New storm packing significant rain, strong winds approaches Bay Area 02:16. California is beginning 2024 with a below-normal mountain snowpack a year after it had one of its best starts in decades ...'}]),\n",
|
|
" (AgentActionMessageLog(tool='tavily_search_results_json', tool_input={'query': 'current weather in San Francisco'}, log=\"\\nInvoking: `tavily_search_results_json` with `{'query': 'current weather in San Francisco'}`\\nresponded: It seems that the search results did not return the current weather in San Francisco. Let me try another method to fetch the weather information for you.\\n\\n\", message_log=[AIMessage(content='It seems that the search results did not return the current weather in San Francisco. Let me try another method to fetch the weather information for you.', additional_kwargs={'function_call': {'name': 'tavily_search_results_json', 'arguments': '{\"query\":\"current weather in San Francisco\"}'}})]),\n",
|
|
" [])],\n",
|
|
" 'agent_outcome': AgentFinish(return_values={'output': \"I'm sorry, but it seems that I'm unable to fetch the current weather information for San Francisco at the moment. I recommend using a weather website or app to get the most up-to-date weather forecast for San Francisco.\"}, log=\"I'm sorry, but it seems that I'm unable to fetch the current weather information for San Francisco at the moment. I recommend using a weather website or app to get the most up-to-date weather forecast for San Francisco.\")}"
|
|
]
|
|
},
|
|
"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 (SF) is characterized by a mild climate with January daytime maximum temperatures averaging around 13°C (55°F). The city experiences microclimates due to its topography and coastal location, leading to significant weather variations across different neighborhoods[1]. Historically, SF has wet winters and dry summers, with average temperatures ranging from the mid-40s to the low 70s Fahrenheit (7-22 degrees Celsius). The warmest months are typically September and October. Fog is frequent, especially in summer, which can lead to cooler temperatures. The rainy season spans from November to March, with an annual average rainfall of about 23 inches (584 mm). Wind is also a notable factor, particularly in coastal areas.\\n\\nReferences:\\n[1] https://www.weather2travel.com/california/san-francisco/january/', '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 mid-40s to the low 70s Fahrenheit (7-22 degrees Celsius), with the warmest months being September and October. Fog is a common occurrence, particularly in the summer, leading to cooler temperatures compared to the surrounding areas. Rainfall is concentrated from November to March, with the city receiving an average of about 23 inches (584 mm) annually. Wind is another factor to consider, as it can be quite strong, especially near the Golden Gate Bridge. It's always advisable to dress in layers when visiting SF due to the potential for rapid weather changes.\"}, log='Reached max steps.')"
|
|
]
|
|
},
|
|
"execution_count": 5,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"workflow = Graph()\n",
|
|
"initial_answer_actor = Actor(\"initial\", initial_chain)\n",
|
|
"next_step_actor = Actor(\"next\", next_chain)\n",
|
|
"finish_actor = Actor(\"finish\", RunnableLambda(finish))\n",
|
|
"tool_actor = Actor(\"tools\", RunnableLambda(execute_tools))\n",
|
|
"\n",
|
|
"# add actors\n",
|
|
"workflow.add_node(initial_answer_actor)\n",
|
|
"workflow.add_node(next_step_actor)\n",
|
|
"workflow.add_node(finish_actor)\n",
|
|
"workflow.add_node(tool_actor)\n",
|
|
"\n",
|
|
"# Enter with initial actor, then loop through tools -> next steps until finished\n",
|
|
"workflow.set_entry_point(initial_answer_actor.key)\n",
|
|
"\n",
|
|
"workflow.add_edge(initial_answer_actor.key, tool_actor.key)\n",
|
|
"workflow.add_conditional_edges(\n",
|
|
" tool_actor.key,\n",
|
|
" lambda x: \"exit\" if len(x['intermediate_steps']) >= 2 else \"continue\",\n",
|
|
" {\n",
|
|
" \"continue\": next_step_actor.key,\n",
|
|
" \"exit\": finish_actor.key\n",
|
|
" }\n",
|
|
")\n",
|
|
"workflow.add_edge(next_step_actor.key, tool_actor.key)\n",
|
|
"workflow.set_finish_point(finish_actor.key)\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.6"
|
|
}
|
|
},
|
|
"nbformat": 4,
|
|
"nbformat_minor": 5
|
|
}
|