[
  {
    "path": ".gitignore",
    "content": "# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# Unit test / coverage reports\nhtmlcov/\n.tox/\n.nox/\n.coverage\n.coverage.*\n.cache\nnosetests.xml\ncoverage.xml\n*.cover\n*.py,cover\n.hypothesis/\n.pytest_cache/\ncover/\n\n# Environments\n.env\n.venv\nenv/\nvenv/\nENV/\nenv.bak/\nvenv.bak/\n\n# Visual Studio Code\n.vscode/*\n!.vscode/settings.json\n!.vscode/tasks.json\n!.vscode/launch.json\n!.vscode/extensions.json\n!.vscode/*.code-snippets\n"
  },
  {
    "path": "Dockerfile",
    "content": "FROM python:3.11\n\nARG openai_key\n\nENV PYTHONBUFFERED 1\nENV OPENAI_API_KEY $openai_key\n\nRUN python -m pip install --upgrade pip\n\nWORKDIR /app\n\nCOPY requirements.txt .\nRUN pip install -r requirements.txt \n\nADD autolang autolang\nCMD [ \"python3\", \"-u\", \"-m\" , \"autolang\"]\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License\n\nCopyright (c) Alvaro Sevilla\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# Autolang\n\nAnother take on BabyAGI, focused on workflows that complete. Powered by langchain. \n\nHere's a simple demo: https://twitter.com/pictobit/status/1645504308874563584\n\n## Running\n\nTo run Autolang, follow these steps:\n\n(Optional) Customize the [tools provided to the agent](autolang/\\_\\_main\\_\\_.py)\n\nInstall dependencies:\n```sh\npip install -r requirements.txt\n```\n\nCopy the `.env.example` file to `.env,` then edit it:\n```sh\ncp .env.example .env\n```\n\nRun the script:\n```sh\npython -m autolang\n```\n\nAlternatively, run with Docker:\n```sh\n./run_docker.sh\n```\n## Architecture\n\nAutolang uses four main components:\n\n<p align=\"center\">\n    <img src=\"assets/diagram.svg\">\n</p>\n\n### Planner\nRuns once at the start, it thinks of a strategy to solve the problem, and produces a task list.\n\n### Executor\nA custom langchain agent, which implements ReAct to solve a single task in the plan. It can be provided any tools in the langchain format.\n\n### Learner\nHere's the interesting part. The system holds an information context string, which starts empty. \nAfter each step, the learner merges the result with the current context, as a sort of medium-term memory\n\n### Reviewer\nAssesses the current task list, based on the current completed tasks and generated info context, and reprioritizes the pending tasks accordingly\n\n## Next steps\nRight now, the main limitation is the limited info context. As a next step, I'm planning on adding a \"long term memory agent\", that extracts information from the context, replacing it with a key. The executor agent will be provided a tool to retrieve these saved snippets if required.\n\n\n"
  },
  {
    "path": "autolang/__main__.py",
    "content": "import os\nimport faiss\nimport readline # for better CLI experience\nfrom typing import List\nfrom langchain import FAISS, InMemoryDocstore\nfrom langchain.agents import Tool, load_tools\nfrom langchain.chat_models import ChatOpenAI\nfrom langchain.embeddings import OpenAIEmbeddings\nfrom langchain.llms.base import BaseLLM \n\nfrom .auto import AutoAgent\nfrom dotenv import load_dotenv\n\n# Load default environment variables (.env)\nload_dotenv()\n\n# API Keys\nOPENAI_API_KEY = os.getenv(\"OPENAI_API_KEY\", default=\"\")\nassert OPENAI_API_KEY, \"OPENAI_API_KEY environment variable is missing from .env\"\n\nOPENAI_API_MODEL = os.getenv(\"OPENAI_API_MODEL\", default=\"gpt-3.5-turbo\")\nassert OPENAI_API_MODEL, \"OPENAI_API_MODEL environment variable is missing from .env\"\n\nobjective = input('What is my purpose? ')\n\n\nllm: BaseLLM = ChatOpenAI(model_name=OPENAI_API_MODEL, temperature=0, request_timeout=120) # type: ignore \nembeddings = OpenAIEmbeddings() # type: ignore\n\n\"\"\"\nCustomize the tools the agent uses here. Here are some others you can add:\n\nos.environ[\"WOLFRAM_ALPHA_APPID\"] = \"<APPID>\"\nos.environ[\"SERPER_API_KEY\"] = \"<KEY>\"\n\ntool_names = [\"terminal\", \"requests\", \"python_repl\", \"human\", \"google-serper\", \"wolfram-alpha\"]\n\"\"\"\n\ntool_names = [\"python_repl\", \"human\"]\n\ntools: List[Tool] = load_tools(tool_names, llm=llm)  # type: ignore\n\nindex = faiss.IndexFlatL2(1536)\ndocstore = InMemoryDocstore({})\nvectorstore = FAISS(embeddings.embed_query, index, docstore, {}) \n\nagent = AutoAgent.from_llm_and_objectives(llm, objective, tools, vectorstore, verbose=True) \n\nagent.run()\n"
  },
  {
    "path": "autolang/agent/base.py",
    "content": "\"\"\"An agent designed to hold a conversation in addition to using tools.\"\"\"\nfrom __future__ import annotations\n\nimport re\nfrom typing import Any, List, Optional, Sequence, Tuple\n\nfrom langchain.agents.agent import Agent\nfrom langchain.callbacks.base import BaseCallbackManager\nfrom langchain.chains import LLMChain\nfrom langchain.llms.base import BaseLLM\nfrom langchain.prompts import PromptTemplate\nfrom langchain.tools.base import BaseTool\n\nfrom .prompt import FORMAT_INSTRUCTIONS, PREFIX, SUFFIX\n\nclass AutonomousAgent(Agent):\n    \"\"\"An agent designed to execute a single task within a larger workflow.\"\"\"\n\n    ai_prefix: str = \"Jarvis\"\n\n    @property\n    def _agent_type(self) -> str:\n        return \"autonomous\"\n\n    @property\n    def observation_prefix(self) -> str:\n        return \"Observation: \"\n\n    @property\n    def llm_prefix(self) -> str:\n        return \"Thought:\"\n\n    @property\n    def finish_tool_name(self) -> str:\n        return self.ai_prefix\n\n    @classmethod\n    def create_prompt(\n        cls,\n        tools: Sequence[BaseTool],\n        prefix: str = PREFIX,\n        suffix: str = SUFFIX,\n        format_instructions: str = FORMAT_INSTRUCTIONS,\n        ai_prefix: str = \"AI\",\n        human_prefix: str = \"Human\",\n        objective: Optional[str] = None,\n        input_variables: Optional[List[str]] = None,\n    ) -> PromptTemplate:\n        tool_strings = \"\\n\".join(\n            [f\"> {tool.name}: {tool.description}\" for tool in tools]\n        )\n        tool_names = \", \".join([tool.name for tool in tools])\n        prefix = prefix.format(objective=objective)\n        format_instructions = format_instructions.format(tool_names=tool_names, ai_prefix=ai_prefix, human_prefix=human_prefix)\n        template = \"\\n\\n\".join([prefix, tool_strings, format_instructions, suffix])\n        input_variables = [\"input\", \"context\", \"agent_scratchpad\"]\n        return PromptTemplate(template=template, input_variables=input_variables)\n\n    def _extract_tool_and_input(self, llm_output: str) -> Optional[Tuple[str, str]]:\n        if f\"{self.ai_prefix}:\" in llm_output:\n            return self.ai_prefix, llm_output.split(f\"{self.ai_prefix}:\")[-1].strip()\n        regex = r\"Action: (.*?)[\\n]*Action Input: (.*)\"\n        match = re.search(regex, llm_output)\n        if not match:\n            raise ValueError(f\"Could not parse LLM output: `{llm_output}`\")\n        action = match.group(1)\n        action_input = match.group(2)\n        return action.strip(), action_input.strip(\" \").strip('\"')\n\n    @classmethod\n    def from_llm_and_tools(\n        cls,\n        llm: BaseLLM,\n        tools: Sequence[BaseTool],\n        objective: Optional[str] = None,\n        callback_manager: Optional[BaseCallbackManager] = None,\n        prefix: str = PREFIX,\n        suffix: str = SUFFIX,\n        format_instructions: str = FORMAT_INSTRUCTIONS,\n        ai_prefix: str = \"Jarvis\",\n        human_prefix: str = \"Human\",\n        input_variables: Optional[List[str]] = None,\n        **kwargs: Any,\n    ) -> \"AutonomousAgent\":\n        \"\"\"Construct an agent from an LLM and tools.\"\"\"\n        cls._validate_tools(tools)\n        prompt = cls.create_prompt(\n            tools,\n            ai_prefix=ai_prefix,\n            human_prefix=human_prefix,\n            prefix=prefix,\n            suffix=suffix,\n            objective=objective,\n            format_instructions=format_instructions,\n            input_variables=input_variables,\n        )\n        llm_chain = LLMChain(\n            llm=llm,\n            prompt=prompt,\n            callback_manager=callback_manager, # type: ignore\n        )\n        tool_names = [tool.name for tool in tools]\n        return cls(llm_chain=llm_chain, allowed_tools=tool_names, ai_prefix=ai_prefix, **kwargs)\n"
  },
  {
    "path": "autolang/agent/prompt.py",
    "content": "from typing import List, Optional, Sequence\n\nfrom langchain.prompts import PromptTemplate\nfrom langchain.tools.base import BaseTool\n\n\nPREFIX = \"\"\"Jarvis is a general purpose AI model trained by OpenAI.\n\nJarvis is tasked with executing a single task within the context of a larger workflow trying to accomplish the following objective: {objective}. It should focus only on the current task, and doesn't attempt to perform further work.\n\nJarvis is constantly learning and improving, and its capabilities are constantly evolving. It is able to process and understand large amounts of text, and can use this knowledge to provide accurate and informative responses to a wide range of questions. \n\nOverall, Jarvis is a powerful tool that can help with a wide range of tasks and provide valuable insights and information on a wide range of topics.\nJarvis is not having a conversation with a user, but rather producing the output of executing a task within a larger workflow.\n\nTOOLS:\n------\n\nJarvis has access to the following tools:\"\"\"\nFORMAT_INSTRUCTIONS = \"\"\"\nThought Process:\n----------------\n\nJarvis always uses the followin thought process and foramt to execute its tasks:\n\n```\nThought: Do I need to use a tool? Yes\nAction: the action to take, should be one of [{tool_names}]\nAction Input: the input to the action\nObservation: the result of the action\n```\n\nWhen Jarvis has a response to say to the Human, or if it doesn't need to use a tool, it always uses the format:\n\n```\nThought: Do I need to use a tool? No\n{ai_prefix}: [your response here]\n```\"\"\"\n\nSUFFIX = \"\"\"Begin!\n\nCurrent context:\n{context}\n\nCurrent task: {input}\n{agent_scratchpad}\"\"\"\n\n\n"
  },
  {
    "path": "autolang/auto.py",
    "content": "from collections import deque\nfrom typing import Any, Dict, List, Optional\nfrom pydantic import BaseModel, Field\nfrom langchain.agents import Tool \nfrom langchain.llms.base import BaseLLM\nfrom langchain.vectorstores import VectorStore\n\nfrom .executor import ExecutionAgent\nfrom .planner import PlanningChain\nfrom .reviewer import ReviewingChain\nfrom .learner import LearningChain\nfrom .printer import print_objective, print_next_task, print_task_list, print_task_result, print_end\n\nclass AutoAgent(BaseModel):\n\n    planning_chain: PlanningChain = Field(...)\n    reviewing_chain: ReviewingChain = Field(...)\n    execution_agent: ExecutionAgent = Field(...)\n    learning_chain: LearningChain = Field(...)\n\n    objective: str = Field(alias=\"objective\")\n    vectorstore: Any = Field(...)\n\n    memory: str = Field(\"\", init=False)\n    complete_list: List[Dict[str, str]] = Field(default_factory=list)\n    pending_list: deque[Dict[str, str]] = Field(default_factory=deque)\n\n    @classmethod\n    def from_llm_and_objectives(\n        cls,\n        llm: BaseLLM,\n        objective: str,\n        tools: List[Tool],\n        vectorstore: VectorStore,\n        verbose: bool = False,\n    ) -> \"AutoAgent\":\n        planning_chain = PlanningChain.from_llm(llm, objective, tools=tools, verbose=verbose)\n        reviewing_chain = ReviewingChain.from_llm(llm, objective, verbose=verbose)\n        execution_agent = ExecutionAgent.from_llm(llm, objective, tools, verbose=verbose)\n        learning_chain = LearningChain.from_llm(llm, objective, verbose=verbose)\n        return cls(\n            objective=objective,\n            planning_chain=planning_chain,\n            reviewing_chain = reviewing_chain,\n            execution_agent=execution_agent,\n            learning_chain=learning_chain,\n            vectorstore=vectorstore,\n        )\n\n    def add_task(self, task: Dict):\n        self.pending_list.append(task)\n\n    def run(self, max_iterations: Optional[int] = None):\n        num_iters = 0\n        print_objective(self.objective)\n\n        self.pending_list = deque(self.planning_chain.generate_tasks())\n\n        while len(self.pending_list) > 0 and (max_iterations is None or num_iters < max_iterations):\n            num_iters += 1\n            print_task_list(self.complete_list, self.pending_list)\n\n            task = self.pending_list.popleft()\n            print_next_task(task)\n\n            result = self.execution_agent.execute_task(task[\"task_name\"], self.memory)\n            if not result: result = \"Empty result\"\n            print_task_result(result)\n\n            self.complete_list.append({\"task_id\": task[\"task_id\"], \"task_name\": task[\"task_name\"]})\n            self.memory = self.learning_chain.update_memory(\n                memory=self.memory,\n                observation=result,\n                completed_tasks=[t[\"task_name\"] for t in self.complete_list],\n                pending_tasks=[t[\"task_name\"] for t in self.pending_list],\n            )\n            self.pending_list = self.reviewing_chain.review_tasks(\n                    this_task_id=len(self.complete_list),\n                    completed_tasks=[t[\"task_name\"] for t in self.complete_list], \n                    pending_tasks=[t[\"task_name\"] for t in self.pending_list], \n                    context=self.memory)\n\n        final_answer = self.execution_agent.execute_task(\"Provide the final answer\", self.memory)\n        print_end(final_answer)\n"
  },
  {
    "path": "autolang/executor.py",
    "content": "from typing import List\nfrom pydantic import BaseModel, Field\nfrom langchain.agents import AgentExecutor, Tool\nfrom langchain.llms.base import BaseLLM\n\nfrom .agent.base import AutonomousAgent\n\nclass ExecutionAgent(BaseModel):\n\n    agent: AgentExecutor = Field(...)\n\n    @classmethod\n    def from_llm(cls, llm: BaseLLM, objective: str, tools: List[Tool], verbose: bool = True) -> \"ExecutionAgent\":\n        agent = AutonomousAgent.from_llm_and_tools(llm=llm, tools=tools, objective=objective, verbose=verbose)\n        agent_executor = AgentExecutor.from_agent_and_tools(agent=agent, tools=tools, verbose=verbose)\n        return cls(agent=agent_executor)\n    \n    def execute_task(self, task: str, context: str) -> str:\n        for i in range(3):\n            try:\n                return self.agent.run({\"input\": task, \"context\": context})\n            except ValueError:\n                print(f\"Value error running executor agent. Will retry {2-i} times\")\n        return \"Failed to execute task.\"\n"
  },
  {
    "path": "autolang/learner.py",
    "content": "from typing import List \nfrom langchain import LLMChain, PromptTemplate\nfrom langchain.llms.base import BaseLLM\n\nlearning_template = \"\"\"Cass is an AI specialized in information consolidation, part of a larger system that is solving a complex problem in multiple steps. Cass is provided the current information context, and the result of the latest step, and updates the context incorporating the result. \nCass is also provided the list of completed and still pending tasks. \nThe rest of the system is provided the task lists and context in the same way, so the context should never contain the tasks themselves\nThe information context is the only persistent memory the system has, after every step, the context must be updated with all relevant informtion, such that the context contains all information needed to complete the objective.\n\nThe ultimate objective is: {objective}.\nCompleted tasks: {completed_tasks}\nThe last task output was:\n{last_output}\n\nThe list of pending tasks: {pending_tasks}\n\nCurrent context to update:\n{context}\n\nCass will generate an updated context. This context will replace the current context.\nCass: \"\"\"\n\nlearning_prompt = lambda objective: PromptTemplate(\n        template=learning_template,\n        partial_variables={\"objective\": objective},\n        input_variables=[\"completed_tasks\", \"pending_tasks\", \"last_output\", \"context\"],\n        )\n\nclass LearningChain(LLMChain):\n\n    @classmethod\n    def from_llm(cls, llm: BaseLLM, objective: str, verbose: bool = True) -> \"LearningChain\":\n        return cls(prompt=learning_prompt(objective), llm=llm, verbose=verbose)\n    \n    def update_memory(self, memory: str, observation: str, completed_tasks: List[str], pending_tasks: List[str]):\n        return self.run(\n                completed_tasks=completed_tasks, \n                pending_tasks=pending_tasks, \n                last_output=observation, \n                context=memory\n                )\n\n\n"
  },
  {
    "path": "autolang/planner.py",
    "content": "from typing import List, Dict \nfrom pydantic import Field\nfrom langchain import LLMChain, PromptTemplate\nfrom langchain.agents import Tool \nfrom langchain.llms.base import BaseLLM\n\nfrom .utils import parse_task_list\n\nplanning_template = \"\"\"You are a task creation AI tasked with generating a full, exhaustive list of tasks to accomplish the following objective: {objective}.\nThe AI system that will execute these tasks will have access to the following tools:\n{tool_strings}\nEach task may only use a single tool, but not all tasks need to use one. The task should not specify the tool. The final task should achieve the objective. \nEach task will be performed by a capable agent, do not break the problem down into too many tasks.\nAim to keep the list short, and never generate more than 5 tasks. Your response should be each task in a separate line, one line per task.\nUse the following format:\n1. First task\n2. Second task\n\"\"\"\n\nplanning_prompt = lambda objective: PromptTemplate(\n        template=planning_template,\n        partial_variables={\"objective\": objective},\n        input_variables=[\"tool_strings\"],\n        )\n\n\nclass PlanningChain(LLMChain):\n\n    tool_strings: str = Field(...)\n\n    @classmethod\n    def from_llm(cls, llm: BaseLLM, objective: str, tools: List[Tool] , verbose: bool = True) -> \"PlanningChain\":\n        tool_strings = \"\\n\".join([f\"> {tool.name}: {tool.description}\" for tool in tools])\n        return cls(prompt=planning_prompt(objective), llm=llm, verbose=verbose, tool_strings=tool_strings)\n    \n    def generate_tasks(self) -> List[Dict]:\n        response = self.run(tool_strings=self.tool_strings)\n        return parse_task_list(response)\n"
  },
  {
    "path": "autolang/printer.py",
    "content": "def print_objective(objective):\n    color_print(\"*****Objective*****\", 4)\n    print(objective)\n\ndef print_task_list(complete_list, pending_list):\n    color_print(\"*****TASK LIST*****\", 5)\n    print(\"Completed: \") \n    for task in complete_list:\n        print(str(task[\"task_id\"]) + \": \" + task[\"task_name\"])\n\n    print(\"\\nPending: \") \n    for task in pending_list:\n        print(str(task[\"task_id\"]) + \": \" + task[\"task_name\"])\n\ndef print_next_task(task):\n    color_print(\"*****NEXT TASK*****\", 2)\n    print(str(task[\"task_id\"]) + \": \" + task[\"task_name\"])\n\ndef print_task_result(result):\n    color_print(\"*****TASK RESULT*****\", 3)\n    print(result)\n\ndef print_end(final_result):\n    color_print(\"*****TASK ENDING*****\", 1)\n    print(final_result)\n\n# leave at the end as the codes somehow screw up indenting in the rest of the file\ndef color_print(text: str, color: int):\n    print(f\"\\n\\033[9{color}m\\033[1m{text}\\033[0m\\033[0m\\n\")\n"
  },
  {
    "path": "autolang/reviewer.py",
    "content": "from collections import deque\nfrom typing import List, Dict \nfrom langchain import LLMChain, PromptTemplate\nfrom langchain.llms.base import BaseLLM\n\nfrom .utils import parse_task_list\n\nreviewing_template = \"\"\"Albus is a task reviewing and prioritization AI, tasked with cleaning the formatting of and reprioritizing the following tasks: {pending_tasks}.\nAlbus is provided with the list of completed tasks, the current pending tasks, and the information context that has been generated so far by the system.\n\nAlbus will decide if the current completed tasks and context are enough to generate a final answer. If this is the case, Albus will notify this using this exact format:\nReview: Can answer\n\nAlbus will never generate the final answer.\nIf there is not enough information to answer, Albus will generate a new list of tasks. The tasks will be ordered by priority, with the most important task first. The tasks will be numbered, starting with {next_task_id}. The following format will be used:\nReview: Must continue\n#. First task\n#. Second task\n\nAlbus will use the current pending tasks to generate this list, but it may remove tasks that are no longer required, or add new ones if strictly required.\n\nThe ultimate objective is: {objective}.\nThe following tasks have already been completed: {completed_tasks}.\nThis is the information context generated so far:\n{context}\n\"\"\"\n\nreviewing_prompt = lambda objective: PromptTemplate(\n        template=reviewing_template,\n        partial_variables={\"objective\": objective},\n        input_variables=[\"completed_tasks\", \"pending_tasks\", \"context\", \"next_task_id\"],\n        )\n\nclass ReviewingChain(LLMChain):\n\n    @classmethod\n    def from_llm(cls, llm: BaseLLM, objective: str, verbose: bool = True) -> \"ReviewingChain\":\n        return cls(prompt=reviewing_prompt(objective), llm=llm, verbose=verbose)\n\n    def review_tasks(self, this_task_id: int, completed_tasks: List[str], pending_tasks: List[str], context: str) -> deque[Dict]:\n        next_task_id = int(this_task_id) + 1\n        response = self.run(completed_tasks=completed_tasks, pending_tasks=pending_tasks, context=context, next_task_id=next_task_id)\n        return deque(parse_task_list(response))\n"
  },
  {
    "path": "autolang/utils.py",
    "content": "\ndef parse_task_list(response):\n    new_tasks = response.split('\\n')\n    prioritized_task_list = []\n    for task_string in new_tasks:\n        if not task_string.strip(): continue\n        task_parts = task_string.strip().split(\".\", 1)\n        if len(task_parts) == 2:\n            task_id = task_parts[0].strip()\n            task_name = task_parts[1].strip()\n            prioritized_task_list.append({\"task_id\": task_id, \"task_name\": task_name})\n    return prioritized_task_list\n"
  },
  {
    "path": "requirements.txt",
    "content": "faiss_cpu==1.7.3\nlangchain==0.0.136\npydantic==1.10.7\nopenai==0.27.4\npython-dotenv==1.0.0"
  },
  {
    "path": "run_docker.sh",
    "content": "docker run -i $(docker build -q . --build-arg openai_key=$OPENAI_API_KEY)\n"
  }
]