diff --git a/docs/modules/agents/auto_agents/examples/autogpt.ipynb b/docs/modules/agents/auto_agents/examples/autogpt.ipynb index 3bbe83ab3f1..6e01302d975 100644 --- a/docs/modules/agents/auto_agents/examples/autogpt.ipynb +++ b/docs/modules/agents/auto_agents/examples/autogpt.ipynb @@ -7,22 +7,13 @@ "metadata": {}, "outputs": [], "source": [ - "from langchain.tools.file_management.write import WriteFileTool" + "from langchain.tools.file_management.write import WriteFileTool\n", + "from langchain.tools.file_management.read import ReadFileTool" ] }, { "cell_type": "code", "execution_count": 2, - "id": "ca13aac1", - "metadata": {}, - "outputs": [], - "source": [ - "tool = WriteFileTool()" - ] - }, - { - "cell_type": "code", - "execution_count": 3, "id": "7c2c9b54", "metadata": {}, "outputs": [], @@ -35,17 +26,18 @@ "search = SerpAPIWrapper()\n", "tools = [\n", " Tool(\n", - " name = \"Search\",\n", + " name = \"search\",\n", " func=search.run,\n", " description=\"useful for when you need to answer questions about current events. You should ask targeted questions\"\n", " ),\n", - " tool,\n", + " WriteFileTool(),\n", + " ReadFileTool(),\n", "]" ] }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 3, "id": "72bc204d", "metadata": {}, "outputs": [], @@ -57,7 +49,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 4, "id": "1df7b724", "metadata": {}, "outputs": [], @@ -74,7 +66,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 5, "id": "709c08c2", "metadata": {}, "outputs": [], @@ -90,7 +82,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 6, "id": "1ab7a3c4", "metadata": {}, "outputs": [], @@ -100,7 +92,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 7, "id": "c032b182", "metadata": { "scrolled": false @@ -115,7 +107,7 @@ "\u001b[1m> Entering new LLMChain chain...\u001b[0m\n", "Prompt after formatting:\n", "\u001b[32;1m\u001b[1;3mSystem: You are Tom, Assistant\n", - "Your decisions must always be made independently without seeking user assistance. Play to your strengths as an LLM and pursue simple strategies with no legal complications.\n", + "Your decisions must always be made independently without seeking user assistance. Play to your strengths as an LLM and pursue simple strategies with no legal complications. If you have completed all your tasks, make sure to use the \"finish\" command.\n", "\n", "GOALS:\n", "\n", @@ -129,8 +121,10 @@ "4. Exclusively use the commands listed in double quotes e.g. \"command name\"\n", "\n", "Commands:\n", - "1. Search: useful for when you need to answer questions about current events. You should ask targeted questions, args: \"tool_input\": \"useful for when you need to answer questions about current events. You should ask targeted questions\"\n", + "1. search: useful for when you need to answer questions about current events. You should ask targeted questions, args: \"tool_input\": \"useful for when you need to answer questions about current events. You should ask targeted questions\"\n", "2. write_file: Write file to disk, args: \"file\": \"\", \"text\": \"\"\n", + "3. read_file: Read file from disk, args: \"file\": \"\"\n", + "4. finish: use this to signal that you have finished all your objectives, args: \"response\": \"final response to let people know you have finished your objectives\"\n", "\n", "Resources:\n", "1. Internet access for searches and information gathering.\n", @@ -162,7 +156,7 @@ " }\n", "} \n", "Ensure the response can be parsed by Python json.loads\n", - "System: The current time and date is Sat Apr 15 11:40:26 2023\n", + "System: The current time and date is Sat Apr 15 14:24:11 2023\n", "System: This reminds you of these events from your past:\n", "[]\n", "\n", @@ -172,16 +166,16 @@ "\u001b[1m> Finished chain.\u001b[0m\n", "{\n", " \"thoughts\": {\n", - " \"text\": \"I need to write a weather report for San Francisco today. I should use the 'search' command to find the current weather conditions.\",\n", - " \"reasoning\": \"I need to gather information about the current weather conditions in San Francisco to write an accurate weather report.\",\n", - " \"plan\": \"- Use the 'search' command to find the current weather conditions in San Francisco.\\n- Write a weather report based on the information gathered.\",\n", + " \"text\": \"I need to write a weather report for SF today. I should use the search command to find the current weather conditions.\",\n", + " \"reasoning\": \"I need to gather information about the current weather conditions in SF to write an accurate weather report.\",\n", + " \"plan\": \"- Use the search command to find the current weather conditions in SF\\n- Write a weather report based on the information gathered\",\n", " \"criticism\": \"I need to make sure that the information I gather is accurate and up-to-date.\",\n", - " \"speak\": \"I will use the 'search' command to find the current weather conditions in San Francisco.\"\n", + " \"speak\": \"I will use the search command to find the current weather conditions in SF.\"\n", " },\n", " \"command\": {\n", " \"name\": \"search\",\n", " \"args\": {\n", - " \"tool_input\": \"San Francisco weather today\"\n", + " \"tool_input\": \"current weather conditions in San Francisco\"\n", " }\n", " }\n", "}\n", @@ -190,7 +184,7 @@ "\u001b[1m> Entering new LLMChain chain...\u001b[0m\n", "Prompt after formatting:\n", "\u001b[32;1m\u001b[1;3mSystem: You are Tom, Assistant\n", - "Your decisions must always be made independently without seeking user assistance. Play to your strengths as an LLM and pursue simple strategies with no legal complications.\n", + "Your decisions must always be made independently without seeking user assistance. Play to your strengths as an LLM and pursue simple strategies with no legal complications. If you have completed all your tasks, make sure to use the \"finish\" command.\n", "\n", "GOALS:\n", "\n", @@ -204,8 +198,10 @@ "4. Exclusively use the commands listed in double quotes e.g. \"command name\"\n", "\n", "Commands:\n", - "1. Search: useful for when you need to answer questions about current events. You should ask targeted questions, args: \"tool_input\": \"useful for when you need to answer questions about current events. You should ask targeted questions\"\n", + "1. search: useful for when you need to answer questions about current events. You should ask targeted questions, args: \"tool_input\": \"useful for when you need to answer questions about current events. You should ask targeted questions\"\n", "2. write_file: Write file to disk, args: \"file\": \"\", \"text\": \"\"\n", + "3. read_file: Read file from disk, args: \"file\": \"\"\n", + "4. finish: use this to signal that you have finished all your objectives, args: \"response\": \"final response to let people know you have finished your objectives\"\n", "\n", "Resources:\n", "1. Internet access for searches and information gathering.\n", @@ -237,43 +233,44 @@ " }\n", "} \n", "Ensure the response can be parsed by Python json.loads\n", - "System: The current time and date is Sat Apr 15 11:40:34 2023\n", + "System: The current time and date is Sat Apr 15 14:24:25 2023\n", "System: This reminds you of these events from your past:\n", - "[Document(page_content='Assistant Reply: {\\n \"thoughts\": {\\n \"text\": \"I need to write a weather report for San Francisco today. I should use the \\'search\\' command to find the current weather conditions.\",\\n \"reasoning\": \"I need to gather information about the current weather conditions in San Francisco to write an accurate weather report.\",\\n \"plan\": \"- Use the \\'search\\' command to find the current weather conditions in San Francisco.\\\\n- Write a weather report based on the information gathered.\",\\n \"criticism\": \"I need to make sure that the information I gather is accurate and up-to-date.\",\\n \"speak\": \"I will use the \\'search\\' command to find the current weather conditions in San Francisco.\"\\n },\\n \"command\": {\\n \"name\": \"search\",\\n \"args\": {\\n \"tool_input\": \"San Francisco weather today\"\\n }\\n }\\n} \\nResult: Unknown command \\'search\\'. Please refer to the \\'COMMANDS\\' list for available commands and only respond in the specified JSON format. ', metadata={})]\n", + "['Assistant Reply: {\\n \"thoughts\": {\\n \"text\": \"I need to write a weather report for SF today. I should use the search command to find the current weather conditions.\",\\n \"reasoning\": \"I need to gather information about the current weather conditions in SF to write an accurate weather report.\",\\n \"plan\": \"- Use the search command to find the current weather conditions in SF\\\\n- Write a weather report based on the information gathered\",\\n \"criticism\": \"I need to make sure that the information I gather is accurate and up-to-date.\",\\n \"speak\": \"I will use the search command to find the current weather conditions in SF.\"\\n },\\n \"command\": {\\n \"name\": \"search\",\\n \"args\": {\\n \"tool_input\": \"current weather conditions in San Francisco\"\\n }\\n }\\n} \\nResult: Command search returned: A few passing clouds, otherwise generally sunny. High 58F. Winds W at 15 to 25 mph. PRECIPITATION. ']\n", "\n", "\n", + "Human: Determine which next command to use, and respond using the format specified above:\n", "AI: {\n", " \"thoughts\": {\n", - " \"text\": \"I need to write a weather report for San Francisco today. I should use the 'search' command to find the current weather conditions.\",\n", - " \"reasoning\": \"I need to gather information about the current weather conditions in San Francisco to write an accurate weather report.\",\n", - " \"plan\": \"- Use the 'search' command to find the current weather conditions in San Francisco.\\n- Write a weather report based on the information gathered.\",\n", + " \"text\": \"I need to write a weather report for SF today. I should use the search command to find the current weather conditions.\",\n", + " \"reasoning\": \"I need to gather information about the current weather conditions in SF to write an accurate weather report.\",\n", + " \"plan\": \"- Use the search command to find the current weather conditions in SF\\n- Write a weather report based on the information gathered\",\n", " \"criticism\": \"I need to make sure that the information I gather is accurate and up-to-date.\",\n", - " \"speak\": \"I will use the 'search' command to find the current weather conditions in San Francisco.\"\n", + " \"speak\": \"I will use the search command to find the current weather conditions in SF.\"\n", " },\n", " \"command\": {\n", " \"name\": \"search\",\n", " \"args\": {\n", - " \"tool_input\": \"San Francisco weather today\"\n", + " \"tool_input\": \"current weather conditions in San Francisco\"\n", " }\n", " }\n", "}\n", - "System: Unknown command 'search'. Please refer to the 'COMMANDS' list for available commands and only respond in the specified JSON format.\n", + "System: Command search returned: A few passing clouds, otherwise generally sunny. High 58F. Winds W at 15 to 25 mph. PRECIPITATION.\n", "Human: Determine which next command to use, and respond using the format specified above:\u001b[0m\n", "\n", "\u001b[1m> Finished chain.\u001b[0m\n", "{\n", " \"thoughts\": {\n", - " \"text\": \"I will use the 'write_file' command to save the weather report to a file.\",\n", - " \"reasoning\": \"I need to save the weather report to a file so that it can be accessed later if needed.\",\n", - " \"plan\": \"- Use the 'write_file' command to save the weather report to a file.\",\n", - " \"criticism\": \"I need to make sure that the file is saved in a location that is easily accessible and that the file name is descriptive.\",\n", - " \"speak\": \"I will use the 'write_file' command to save the weather report to a file.\"\n", + " \"text\": \"I have the current weather conditions for SF. I should write a weather report based on this information. I will use the write_file command to save the weather report to disk.\",\n", + " \"reasoning\": \"I need to write a weather report based on the information gathered from the search command.\",\n", + " \"plan\": \"- Use the write_file command to save the weather report to disk\",\n", + " \"criticism\": \"I need to make sure that the weather report is accurate and well-written.\",\n", + " \"speak\": \"I will use the write_file command to save the weather report to disk.\"\n", " },\n", " \"command\": {\n", " \"name\": \"write_file\",\n", " \"args\": {\n", - " \"file\": \"weather_report_sf.txt\",\n", - " \"text\": \"The current weather in San Francisco is sunny with a high of 70 degrees Fahrenheit and a low of 55 degrees Fahrenheit.\"\n", + " \"file\": \"weather_report.txt\",\n", + " \"text\": \"The current weather conditions in San Francisco are as follows: A few passing clouds, otherwise generally sunny. High 58F. Winds W at 15 to 25 mph. PRECIPITATION.\"\n", " }\n", " }\n", "}\n", @@ -288,7 +285,7 @@ "text": [ "Prompt after formatting:\n", "\u001b[32;1m\u001b[1;3mSystem: You are Tom, Assistant\n", - "Your decisions must always be made independently without seeking user assistance. Play to your strengths as an LLM and pursue simple strategies with no legal complications.\n", + "Your decisions must always be made independently without seeking user assistance. Play to your strengths as an LLM and pursue simple strategies with no legal complications. If you have completed all your tasks, make sure to use the \"finish\" command.\n", "\n", "GOALS:\n", "\n", @@ -302,8 +299,10 @@ "4. Exclusively use the commands listed in double quotes e.g. \"command name\"\n", "\n", "Commands:\n", - "1. Search: useful for when you need to answer questions about current events. You should ask targeted questions, args: \"tool_input\": \"useful for when you need to answer questions about current events. You should ask targeted questions\"\n", + "1. search: useful for when you need to answer questions about current events. You should ask targeted questions, args: \"tool_input\": \"useful for when you need to answer questions about current events. You should ask targeted questions\"\n", "2. write_file: Write file to disk, args: \"file\": \"\", \"text\": \"\"\n", + "3. read_file: Read file from disk, args: \"file\": \"\"\n", + "4. finish: use this to signal that you have finished all your objectives, args: \"response\": \"final response to let people know you have finished your objectives\"\n", "\n", "Resources:\n", "1. Internet access for searches and information gathering.\n", @@ -335,30 +334,75 @@ " }\n", "} \n", "Ensure the response can be parsed by Python json.loads\n", - "System: The current time and date is Sat Apr 15 11:40:42 2023\n", + "System: The current time and date is Sat Apr 15 14:24:32 2023\n", "System: This reminds you of these events from your past:\n", - "[Document(page_content='Assistant Reply: {\\n \"thoughts\": {\\n \"text\": \"I will use the \\'write_file\\' command to save the weather report to a file.\",\\n \"reasoning\": \"I need to save the weather report to a file so that it can be accessed later if needed.\",\\n \"plan\": \"- Use the \\'write_file\\' command to save the weather report to a file.\",\\n \"criticism\": \"I need to make sure that the file is saved in a location that is easily accessible and that the file name is descriptive.\",\\n \"speak\": \"I will use the \\'write_file\\' command to save the weather report to a file.\"\\n },\\n \"command\": {\\n \"name\": \"write_file\",\\n \"args\": {\\n \"file\": \"weather_report_sf.txt\",\\n \"text\": \"The current weather in San Francisco is sunny with a high of 70 degrees Fahrenheit and a low of 55 degrees Fahrenheit.\"\\n }\\n }\\n} \\nResult: Command write_file returned: Done ', metadata={}), Document(page_content='Assistant Reply: {\\n \"thoughts\": {\\n \"text\": \"I need to write a weather report for San Francisco today. I should use the \\'search\\' command to find the current weather conditions.\",\\n \"reasoning\": \"I need to gather information about the current weather conditions in San Francisco to write an accurate weather report.\",\\n \"plan\": \"- Use the \\'search\\' command to find the current weather conditions in San Francisco.\\\\n- Write a weather report based on the information gathered.\",\\n \"criticism\": \"I need to make sure that the information I gather is accurate and up-to-date.\",\\n \"speak\": \"I will use the \\'search\\' command to find the current weather conditions in San Francisco.\"\\n },\\n \"command\": {\\n \"name\": \"search\",\\n \"args\": {\\n \"tool_input\": \"San Francisco weather today\"\\n }\\n }\\n} \\nResult: Unknown command \\'search\\'. Please refer to the \\'COMMANDS\\' list for available commands and only respond in the specified JSON format. ', metadata={})]\n", + "['Assistant Reply: {\\n \"thoughts\": {\\n \"text\": \"I have the current weather conditions for SF. I should write a weather report based on this information. I will use the write_file command to save the weather report to disk.\",\\n \"reasoning\": \"I need to write a weather report based on the information gathered from the search command.\",\\n \"plan\": \"- Use the write_file command to save the weather report to disk\",\\n \"criticism\": \"I need to make sure that the weather report is accurate and well-written.\",\\n \"speak\": \"I will use the write_file command to save the weather report to disk.\"\\n },\\n \"command\": {\\n \"name\": \"write_file\",\\n \"args\": {\\n \"file\": \"weather_report.txt\",\\n \"text\": \"The current weather conditions in San Francisco are as follows: A few passing clouds, otherwise generally sunny. High 58F. Winds W at 15 to 25 mph. PRECIPITATION.\"\\n }\\n }\\n} \\nResult: Command write_file returned: File written to successfully. ', 'Assistant Reply: {\\n \"thoughts\": {\\n \"text\": \"I need to write a weather report for SF today. I should use the search command to find the current weather conditions.\",\\n \"reasoning\": \"I need to gather information about the current weather conditions in SF to write an accurate weather report.\",\\n \"plan\": \"- Use the search command to find the current weather conditions in SF\\\\n- Write a weather report based on the information gathered\",\\n \"criticism\": \"I need to make sure that the information I gather is accurate and up-to-date.\",\\n \"speak\": \"I will use the search command to find the current weather conditions in SF.\"\\n },\\n \"command\": {\\n \"name\": \"search\",\\n \"args\": {\\n \"tool_input\": \"current weather conditions in San Francisco\"\\n }\\n }\\n} \\nResult: Command search returned: A few passing clouds, otherwise generally sunny. High 58F. Winds W at 15 to 25 mph. PRECIPITATION. ']\n", "\n", "\n", + "Human: Determine which next command to use, and respond using the format specified above:\n", "AI: {\n", " \"thoughts\": {\n", - " \"text\": \"I will use the 'write_file' command to save the weather report to a file.\",\n", - " \"reasoning\": \"I need to save the weather report to a file so that it can be accessed later if needed.\",\n", - " \"plan\": \"- Use the 'write_file' command to save the weather report to a file.\",\n", - " \"criticism\": \"I need to make sure that the file is saved in a location that is easily accessible and that the file name is descriptive.\",\n", - " \"speak\": \"I will use the 'write_file' command to save the weather report to a file.\"\n", + " \"text\": \"I need to write a weather report for SF today. I should use the search command to find the current weather conditions.\",\n", + " \"reasoning\": \"I need to gather information about the current weather conditions in SF to write an accurate weather report.\",\n", + " \"plan\": \"- Use the search command to find the current weather conditions in SF\\n- Write a weather report based on the information gathered\",\n", + " \"criticism\": \"I need to make sure that the information I gather is accurate and up-to-date.\",\n", + " \"speak\": \"I will use the search command to find the current weather conditions in SF.\"\n", + " },\n", + " \"command\": {\n", + " \"name\": \"search\",\n", + " \"args\": {\n", + " \"tool_input\": \"current weather conditions in San Francisco\"\n", + " }\n", + " }\n", + "}\n", + "System: Command search returned: A few passing clouds, otherwise generally sunny. High 58F. Winds W at 15 to 25 mph. PRECIPITATION.\n", + "Human: Determine which next command to use, and respond using the format specified above:\n", + "AI: {\n", + " \"thoughts\": {\n", + " \"text\": \"I have the current weather conditions for SF. I should write a weather report based on this information. I will use the write_file command to save the weather report to disk.\",\n", + " \"reasoning\": \"I need to write a weather report based on the information gathered from the search command.\",\n", + " \"plan\": \"- Use the write_file command to save the weather report to disk\",\n", + " \"criticism\": \"I need to make sure that the weather report is accurate and well-written.\",\n", + " \"speak\": \"I will use the write_file command to save the weather report to disk.\"\n", " },\n", " \"command\": {\n", " \"name\": \"write_file\",\n", " \"args\": {\n", - " \"file\": \"weather_report_sf.txt\",\n", - " \"text\": \"The current weather in San Francisco is sunny with a high of 70 degrees Fahrenheit and a low of 55 degrees Fahrenheit.\"\n", + " \"file\": \"weather_report.txt\",\n", + " \"text\": \"The current weather conditions in San Francisco are as follows: A few passing clouds, otherwise generally sunny. High 58F. Winds W at 15 to 25 mph. PRECIPITATION.\"\n", " }\n", " }\n", "}\n", - "System: Command write_file returned: Done\n", - "Human: Determine which next command to use, and respond using the format specified above:\u001b[0m\n" + "System: Command write_file returned: File written to successfully.\n", + "Human: Determine which next command to use, and respond using the format specified above:\u001b[0m\n", + "\n", + "\u001b[1m> Finished chain.\u001b[0m\n", + "{\n", + " \"thoughts\": {\n", + " \"text\": \"I have completed all my tasks. I will use the finish command to signal that I am done.\",\n", + " \"reasoning\": \"I have completed the task of writing a weather report for SF today.\",\n", + " \"plan\": \"- Use the finish command to signal that I am done.\",\n", + " \"criticism\": \"I need to make sure that I have completed all my tasks before using the finish command.\",\n", + " \"speak\": \"I will use the finish command to signal that I am done.\"\n", + " },\n", + " \"command\": {\n", + " \"name\": \"finish\",\n", + " \"args\": {\n", + " \"response\": \"I have completed all my tasks.\"\n", + " }\n", + " }\n", + "}\n" ] + }, + { + "data": { + "text/plain": [ + "'I have completed all my tasks.'" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" } ], "source": [ @@ -368,7 +412,7 @@ { "cell_type": "code", "execution_count": null, - "id": "32710d40", + "id": "aa264f26", "metadata": {}, "outputs": [], "source": [] diff --git a/langchain/auto_agents/autogpt/agent.py b/langchain/auto_agents/autogpt/agent.py index f7f28389867..ae6688a8b34 100644 --- a/langchain/auto_agents/autogpt/agent.py +++ b/langchain/auto_agents/autogpt/agent.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from typing import List, Optional from langchain.auto_agents.autogpt.output_parser import ( @@ -5,28 +7,26 @@ from langchain.auto_agents.autogpt.output_parser import ( BaseAutoGPTOutputParser, ) from langchain.auto_agents.autogpt.prompt import AutoGPTPrompt +from langchain.auto_agents.autogpt.prompt_generator import FINISH_NAME from langchain.chains.llm import LLMChain from langchain.chat_models.base import BaseChatModel -from langchain.schema import AIMessage, Document, HumanMessage, SystemMessage +from langchain.schema import ( + AIMessage, + BaseMessage, + Document, + HumanMessage, + SystemMessage, +) from langchain.tools.base import BaseTool from langchain.vectorstores.base import VectorStoreRetriever -class Agent: - """Agent class for interacting with Auto-GPT. - - Attributes: - ai_name: The name of the agent. - memory: The memory object to use. - full_message_history: The full message history. - next_action_count: The number of actions to execute. - prompt: The prompt to use. - user_input: The user input. - """ +class AutoGPT: + """Agent class for interacting with Auto-GPT.""" def __init__( self, - ai_name, + ai_name: str, memory: VectorStoreRetriever, chain: LLMChain, output_parser: BaseAutoGPTOutputParser, @@ -34,7 +34,7 @@ class Agent: ): self.ai_name = ai_name self.memory = memory - self.full_message_history = [] + self.full_message_history: List[BaseMessage] = [] self.next_action_count = 0 self.chain = chain self.output_parser = output_parser @@ -49,19 +49,20 @@ class Agent: tools: List[BaseTool], llm: BaseChatModel, output_parser: Optional[BaseAutoGPTOutputParser] = None, - ): + ) -> AutoGPT: prompt = AutoGPTPrompt( ai_name=ai_name, ai_role=ai_role, tools=tools, input_variables=["memory", "messages", "goals", "user_input"], + token_counter=llm.get_num_tokens, ) chain = LLMChain(llm=llm, prompt=prompt) return cls( ai_name, memory, chain, output_parser or AutoGPTOutputParser(), tools ) - def run(self, goals: List[str]): + def run(self, goals: List[str]) -> str: user_input = "Determine which next command to use, and respond using the format specified above:" # Interaction Loop loop_count = 0 @@ -85,6 +86,8 @@ class Agent: # Get command name and arguments action = self.output_parser.parse(assistant_reply) tools = {t.name: t for t in self.tools} + if action.name == FINISH_NAME: + return action.args["response"] if action.name in tools: tool = tools[action.name] observation = tool.call(action.args) diff --git a/langchain/auto_agents/autogpt/output_parser.py b/langchain/auto_agents/autogpt/output_parser.py index 208f788a4cf..0fe3954f74b 100644 --- a/langchain/auto_agents/autogpt/output_parser.py +++ b/langchain/auto_agents/autogpt/output_parser.py @@ -1,7 +1,8 @@ import json -from typing import Dict, NamedTuple, Union +from abc import abstractmethod +from typing import Dict, NamedTuple -from langchain.schema import AgentAction, AgentFinish, BaseOutputParser +from langchain.schema import BaseOutputParser class AutoGPTAction(NamedTuple): @@ -10,6 +11,7 @@ class AutoGPTAction(NamedTuple): class BaseAutoGPTOutputParser(BaseOutputParser): + @abstractmethod def parse(self, text: str) -> AutoGPTAction: """Return AutoGPTAction""" diff --git a/langchain/auto_agents/autogpt/prompt.py b/langchain/auto_agents/autogpt/prompt.py index 4bda3903e02..43694c64b63 100644 --- a/langchain/auto_agents/autogpt/prompt.py +++ b/langchain/auto_agents/autogpt/prompt.py @@ -1,17 +1,13 @@ import time -from typing import Any, List +from typing import Any, Callable, List -from pydantic import BaseModel, Field +from pydantic import BaseModel from langchain.auto_agents.autogpt.prompt_generator import get_prompt from langchain.prompts.chat import ( BaseChatPromptTemplate, - ChatMessagePromptTemplate, - ChatPromptTemplate, - HumanMessagePromptTemplate, - SystemMessagePromptTemplate, ) -from langchain.schema import BaseMessage, ChatMessage, HumanMessage, SystemMessage +from langchain.schema import BaseMessage, HumanMessage, SystemMessage from langchain.tools.base import BaseToolInterface from langchain.vectorstores.base import VectorStoreRetriever @@ -20,19 +16,11 @@ class AutoGPTPrompt(BaseChatPromptTemplate, BaseModel): ai_name: str ai_role: str tools: List[BaseToolInterface] + token_counter: Callable[[str], int] + send_token_limit: int = 4196 - def construct_full_prompt(self, goals) -> str: - """ - Returns a prompt to the user with the class information in an organized fashion. - - Parameters: - None - - Returns: - full_prompt (str): A string containing the initial prompt for the user including the ai_name, ai_role and ai_goals. - """ - - prompt_start = """Your decisions must always be made independently without seeking user assistance. Play to your strengths as an LLM and pursue simple strategies with no legal complications.""" + def construct_full_prompt(self, goals: List[str]) -> str: + prompt_start = """Your decisions must always be made independently without seeking user assistance. Play to your strengths as an LLM and pursue simple strategies with no legal complications. If you have completed all your tasks, make sure to use the "finish" command.""" # Construct full prompt full_prompt = ( @@ -45,21 +33,37 @@ class AutoGPTPrompt(BaseChatPromptTemplate, BaseModel): return full_prompt def format_messages(self, **kwargs: Any) -> List[BaseMessage]: - messages = [] + base_prompt = SystemMessage(content=self.construct_full_prompt(kwargs["goals"])) + time_prompt = SystemMessage( + content=f"The current time and date is {time.strftime('%c')}" + ) + used_tokens = self.token_counter(base_prompt.content) + self.token_counter( + time_prompt.content + ) memory: VectorStoreRetriever = kwargs["memory"] previous_messages = kwargs["messages"] - relevant_memory = memory.get_relevant_documents(str(previous_messages[-2:])) - messages.append( - SystemMessage(content=self.construct_full_prompt(kwargs["goals"])) + relevant_docs = memory.get_relevant_documents(str(previous_messages[-10:])) + relevant_memory = [d.page_content for d in relevant_docs] + relevant_memory_tokens = sum( + [self.token_counter(doc) for doc in relevant_memory] ) - messages.append( - SystemMessage(content=f"The current time and date is {time.strftime('%c')}") - ) - messages.append( - SystemMessage( - content=f"This reminds you of these events from your past:\n{relevant_memory}\n\n" + while used_tokens + relevant_memory_tokens > 2500: + relevant_memory = relevant_memory[:-1] + relevant_memory_tokens = sum( + [self.token_counter(doc) for doc in relevant_memory] ) + memory_message = SystemMessage( + content=f"This reminds you of these events from your past:\n{relevant_memory}\n\n" ) - messages.extend(previous_messages[-2:]) - messages.append(HumanMessage(content=kwargs["user_input"])) + used_tokens += len(memory_message.content) + historical_messages: List[BaseMessage] = [] + for message in previous_messages[-10:][::-1]: + message_tokens = self.token_counter(message.content) + if used_tokens + message_tokens > self.send_token_limit - 1000: + break + historical_messages = [message] + historical_messages + input_message = HumanMessage(content=kwargs["user_input"]) + messages: List[BaseMessage] = [base_prompt, time_prompt, memory_message] + messages += historical_messages + messages.append(input_message) return messages diff --git a/langchain/auto_agents/autogpt/prompt_generator.py b/langchain/auto_agents/autogpt/prompt_generator.py index 8b9a87a810b..ac4af8f5e7b 100644 --- a/langchain/auto_agents/autogpt/prompt_generator.py +++ b/langchain/auto_agents/autogpt/prompt_generator.py @@ -1,21 +1,27 @@ import json +from typing import List -from langchain.tools.base import BaseTool +from langchain.tools.base import BaseToolInterface + +FINISH_NAME = "finish" class PromptGenerator: - """ - A class for generating custom prompt strings based on constraints, commands, resources, and performance evaluations. + """A class for generating custom prompt strings. + + Does this based on constraints, commands, resources, and performance evaluations. """ - def __init__(self): + def __init__(self) -> None: + """Initialize the PromptGenerator object. + + Starts with empty lists of constraints, commands, resources, + and performance evaluations. """ - Initialize the PromptGenerator object with empty lists of constraints, commands, resources, and performance evaluations. - """ - self.constraints = [] - self.commands = [] - self.resources = [] - self.performance_evaluation = [] + self.constraints: List[str] = [] + self.commands: List[BaseToolInterface] = [] + self.resources: List[str] = [] + self.performance_evaluation: List[str] = [] self.response_format = { "thoughts": { "text": "thought", @@ -27,7 +33,7 @@ class PromptGenerator: "command": {"name": "command name", "args": {"arg name": "value"}}, } - def add_constraint(self, constraint): + def add_constraint(self, constraint: str) -> None: """ Add a constraint to the constraints list. @@ -36,16 +42,16 @@ class PromptGenerator: """ self.constraints.append(constraint) - def add_tool(self, tool: BaseTool): + def add_tool(self, tool: BaseToolInterface) -> None: self.commands.append(tool) - def _generate_command_string(self, tool): + def _generate_command_string(self, tool: BaseToolInterface) -> str: args_string = ", ".join( f'"{key}": "{value}"' for key, value in tool.args.items() ) return f"{tool.name}: {tool.description}, args: {args_string}" - def add_resource(self, resource): + def add_resource(self, resource: str) -> None: """ Add a resource to the resources list. @@ -54,7 +60,7 @@ class PromptGenerator: """ self.resources.append(resource) - def add_performance_evaluation(self, evaluation): + def add_performance_evaluation(self, evaluation: str) -> None: """ Add a performance evaluation item to the performance_evaluation list. @@ -63,7 +69,7 @@ class PromptGenerator: """ self.performance_evaluation.append(evaluation) - def _generate_numbered_list(self, items, item_type="list"): + def _generate_numbered_list(self, items: list, item_type: str = "list") -> str: """ Generate a numbered list from given items based on the item_type. @@ -75,14 +81,20 @@ class PromptGenerator: str: The formatted numbered list. """ if item_type == "command": - return "\n".join( - f"{i+1}. {self._generate_command_string(item)}" + command_strings = [ + f"{i + 1}. {self._generate_command_string(item)}" for i, item in enumerate(items) + ] + finish_description = ( + "use this to signal that you have finished all your objectives" ) + finish_args = '"response": "final response to let people know you have finished your objectives"' + finish_string = f"{len(items) + 1}. {FINISH_NAME}: {finish_description}, args: {finish_args}" + return "\n".join(command_strings + [finish_string]) else: return "\n".join(f"{i+1}. {item}" for i, item in enumerate(items)) - def generate_prompt_string(self): + def generate_prompt_string(self) -> str: """ Generate a prompt string based on the constraints, commands, resources, and performance evaluations. @@ -101,9 +113,10 @@ class PromptGenerator: return prompt_string -def get_prompt(tools): - """ - This function generates a prompt string that includes various constraints, commands, resources, and performance evaluations. +def get_prompt(tools: List[BaseToolInterface]) -> str: + """This function generates a prompt string. + + It includes various constraints, commands, resources, and performance evaluations. Returns: str: The generated prompt string. diff --git a/langchain/tools/base.py b/langchain/tools/base.py index 54eaf898319..43a17fe7f78 100644 --- a/langchain/tools/base.py +++ b/langchain/tools/base.py @@ -149,7 +149,7 @@ class BaseTool(BaseToolInterface): """Run the tool.""" return self.call({"tool_input": tool_input}, **kwargs) - async def arun(self, tool_input: str, **kwargs) -> str: + async def arun(self, tool_input: str, **kwargs: Any) -> str: """Run the tool asynchronously.""" observation = await self.acall({"tool_input": tool_input}, **kwargs) return observation diff --git a/langchain/tools/file_management/read.py b/langchain/tools/file_management/read.py new file mode 100644 index 00000000000..0fa6faed8c2 --- /dev/null +++ b/langchain/tools/file_management/read.py @@ -0,0 +1,20 @@ +from typing import Dict + +from langchain.tools.base import BaseMultiArgTool + + +class ReadFileTool(BaseMultiArgTool): + name: str = "read_file" + tool_args: Dict[str, str] = {"file": ""} + description: str = "Read file from disk" + + def _run(self, file: str) -> str: + try: + with open(file, "r", encoding="utf-8") as f: + content = f.read() + return content + except Exception as e: + return "Error: " + str(e) + + async def _arun(self, tool_input: str) -> str: + raise NotImplementedError diff --git a/langchain/tools/file_management/write.py b/langchain/tools/file_management/write.py index d33354bb6bb..0319e13685d 100644 --- a/langchain/tools/file_management/write.py +++ b/langchain/tools/file_management/write.py @@ -1,33 +1,24 @@ -import json import os from typing import Dict -from pydantic import root_validator - from langchain.tools.base import BaseMultiArgTool -def write_to_file(filename, text): - """Write text to a file""" - try: - directory = os.path.dirname(filename) - if not os.path.exists(directory): - os.makedirs(directory) - with open(filename, "w", encoding="utf-8") as f: - f.write(text) - return "File written to successfully." - except Exception as e: - return "Error: " + str(e) - - class WriteFileTool(BaseMultiArgTool): name: str = "write_file" tool_args: Dict[str, str] = {"file": "", "text": ""} description: str = "Write file to disk" - def _run(self, file, text) -> str: - write_to_file(file, text) - return "Done" + def _run(self, file: str, text: str) -> str: + try: + directory = os.path.dirname(file) + if not os.path.exists(directory) and directory: + os.makedirs(directory) + with open(file, "w", encoding="utf-8") as f: + f.write(text) + return "File written to successfully." + except Exception as e: + return "Error: " + str(e) async def _arun(self, tool_input: str) -> str: raise NotImplementedError