This commit is contained in:
Harrison Chase
2024-01-05 16:01:08 -08:00
committed by Nuno Campos
parent 6a93a68fc3
commit 0f6ea1fe4b
2 changed files with 293 additions and 12 deletions
+290 -4
View File
@@ -1,9 +1,17 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "396e20d9-8684-40ea-a46a-e3dfa36ed5a6",
"metadata": {},
"source": [
"## Existing Agent Executor"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "53b9dce4-e4ae-4bdb-b752-0f04350a2e3d",
"execution_count": 1,
"id": "d642e6af-217a-4414-a78c-509b44155eca",
"metadata": {},
"outputs": [],
"source": [
@@ -69,15 +77,293 @@
"\n",
"# Define entry point and execute the graph\n",
"workflow.set_entry_point(llm_agent)\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.weather2travel.com/california/san-francisco/january/',\n",
" 'content': 'San Francisco weather in January 2024 Expect 13°C daytime maximum temperatures long-term weather averages for San Francisco in January before you book your next holiday to California in 2024/2025. San Francisco January sunrise & sunset times How sunny is it in San Francisco in January?San Francisco weather in January 2024 Expect 13°C daytime maximum temperatures in the shade with on average 6 hours of sunshine per day in San Francisco in January. Check more long-term weather averages for San Francisco in January before you book your next holiday to California in 2024/2025. 13 13°C max day temperature 6'}])],\n",
" 'agent_outcome': AgentFinish(return_values={'output': 'The weather in San Francisco in January 2024 is expected to have a daytime maximum temperature of 13°C with an average of 6 hours of sunshine per day. You can find more long-term weather averages for San Francisco in January on this [website](https://www.weather2travel.com/california/san-francisco/january/).'}, log='The weather in San Francisco in January 2024 is expected to have a daytime maximum temperature of 13°C with an average of 6 hours of sunshine per day. You can find more long-term weather averages for San Francisco in January on this [website](https://www.weather2travel.com/california/san-francisco/january/).')}"
]
},
"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": 14,
"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": 21,
"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": 22,
"id": "d6cdd1cd-e480-4dd7-99b4-9018eb243b4d",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"AgentFinish(return_values={'output': \"The current weather in San Francisco is characterized by a daytime maximum temperature of 13°C in January, with an average of 6 hours of sunshine per day and 10 days of some rainfall. Nighttime temperatures typically drop to around 7°C. There is no significant heat and humidity, and the monthly rainfall averages at 125 mm. The UV index is low at 2, and the sea temperature averages at 11°C. These conditions reflect San Francisco's Mediterranean-like climate, with its wet winters and dry summers. However, weather can vary between neighborhoods due to microclimates, so localized weather advisories should be checked. For real-time updates and specific forecasts, including any weather advisories, it is recommended to consult a reliable weather service[1].\\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, often rolling in during the evening and clearing by midday. Rainfall is most frequent from November to March, while the rest of the year is relatively dry. Wind is another constant factor, with the afternoon sea breeze being a defining feature of the city's weather. Despite these general patterns, it's always advisable to check the current weather forecast before planning activities in San Francisco, as conditions can change rapidly.\"}, log='Reached max steps.')"
]
},
"execution_count": 22,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"workflow = Graph()\n",
"decision_point = DecisionPoint(\"exit?\", lambda x: \"exit\" if len(x['intermediate_steps']) >= 2 else \"continue\")\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",
"# Register actors\n",
"workflow.register(initial_answer_actor)\n",
"workflow.register(next_step_actor)\n",
"workflow.register(finish_actor)\n",
"workflow.register(decision_point)\n",
"workflow.register(tool_actor)\n",
"\n",
"# Define connections with conditional logic\n",
"workflow.connect(initial_answer_actor, tool_actor)\n",
"workflow.branch(decision_point, next_step_actor, condition=\"continue\")\n",
"workflow.connect(next_step_actor, tool_actor)\n",
"workflow.branch(decision_point, finish_actor, condition=\"exit\") # Exit the workflow\n",
"workflow.connect(tool_actor, decision_point)\n",
"\n",
"# Define entry point and execute the graph\n",
"workflow.set_entry_point(initial_answer_actor)\n",
"workflow.set_finish_point(finish_actor)\n",
"chain = workflow.compile()\n",
"\n",
"chain.invoke({\"input\": \"what is the weather in sf\", \"intermediate_steps\": []})"
"chain.invoke({\"question\": \"what is the weather in sf\", \"intermediate_steps\": []})"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2515edbc-a9b6-42ce-bb5e-f1f2503d1bb4",
"id": "9babf196-b1fd-492d-9197-96a674f5e81d",
"metadata": {},
"outputs": [],
"source": []
+3 -8
View File
@@ -30,7 +30,6 @@ class Graph:
self.connections = {}
self.branches = {}
self.entry_point: Optional[str] = None
self.finish_points = set()
def register(self, node: Union[Actor, DecisionPoint]):
if node.name in self.nodes:
@@ -66,12 +65,12 @@ class Graph:
def set_finish_point(self, node: Actor):
if node.name not in self.nodes:
raise ValueError(f"Need to register `{node.name}` first")
self.finish_points |= node.name
self.connections[node.name] = "end"
def compile(self):
# Validate all nodes have an entry point
all_nodes = set(self.nodes)
all_entry_points = set(self.connections).union(self.branches).union(self.finish_points)
all_entry_points = set(self.connections).union(self.branches)
branch_ends = set()
for v in self.branches.values():
branch_ends.update(v.values())
@@ -93,12 +92,8 @@ class Graph:
start: Channel.subscribe_to(start) | (lambda x: branch(x, self.nodes[start].callable, mapping))
for start, mapping in self.branches.items()
}
endings = {
end: Channel.subscribe_to(end) | self.nodes[end].runnable | Channel.write_to("end")
for end in self.finish_points
}
app = Pregel(
chains = {**chains, **decisions, **endings},
chains = {**chains, **decisions},
input=self.entry_point,
output="end"
)