├── .gitignore ├── LICENSE ├── README.md └── app.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # poetry 98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 102 | #poetry.lock 103 | 104 | # pdm 105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 106 | #pdm.lock 107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 108 | # in version control. 109 | # https://pdm.fming.dev/#use-with-ide 110 | .pdm.toml 111 | 112 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 113 | __pypackages__/ 114 | 115 | # Celery stuff 116 | celerybeat-schedule 117 | celerybeat.pid 118 | 119 | # SageMath parsed files 120 | *.sage.py 121 | 122 | # Environments 123 | .env 124 | .venv 125 | env/ 126 | venv/ 127 | ENV/ 128 | env.bak/ 129 | venv.bak/ 130 | 131 | # Spyder project settings 132 | .spyderproject 133 | .spyproject 134 | 135 | # Rope project settings 136 | .ropeproject 137 | 138 | # mkdocs documentation 139 | /site 140 | 141 | # mypy 142 | .mypy_cache/ 143 | .dmypy.json 144 | dmypy.json 145 | 146 | # Pyre type checker 147 | .pyre/ 148 | 149 | # pytype static type analyzer 150 | .pytype/ 151 | 152 | # Cython debug symbols 153 | cython_debug/ 154 | 155 | # PyCharm 156 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 157 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 158 | # and can be added to the global gitignore or merged into this file. For a more nuclear 159 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 160 | #.idea/ 161 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Avra 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BabyAGI-Web-App 2 | LangChain Baby AGI integrated as a Web App using Databutton 3 | 4 | The live demo app is hosted over [here](https://databutton.com/v/lgzxq112/Baby_AGI) 5 | 6 | Blog - [here](https://medium.com/@avra42/langchain-autonomous-agent-baby-agi-integrated-as-web-app-7a826fefad7d) 7 | 8 | Video - [here](https://youtu.be/cvAwOGfeHgw) 9 | 10 | # References 11 | - [BabyAGI original repo](https://github.com/yoheinakajima/babyagi)  12 | - [BabyAGI paper](https://yoheinakajima.com/task-driven-autonomous-agent-utilizing-gpt-4-pinecone-and-langchain-for-diverse-applications/)  13 | - [LangChain Baby AGI](https://python.langchain.com/en/latest/use_cases/autonomous_agents.html) 14 | - [Databutton](https://www.databutton.io/) 15 | -------------------------------------------------------------------------------- /app.py: -------------------------------------------------------------------------------- 1 | # https://python.langchain.com/en/latest/use_cases/agents/baby_agi.html 2 | # Import necessary packages 3 | from collections import deque 4 | from typing import Dict, List, Optional 5 | 6 | import streamlit as st 7 | from langchain import LLMChain, OpenAI, PromptTemplate 8 | from langchain.embeddings.openai import OpenAIEmbeddings 9 | from langchain.llms import BaseLLM 10 | from langchain.vectorstores import FAISS 11 | from langchain.vectorstores.base import VectorStore 12 | from pydantic import BaseModel, Field 13 | 14 | 15 | class TaskCreationChain(LLMChain): 16 | @classmethod 17 | def from_llm(cls, llm: BaseLLM, objective: str, verbose: bool = True) -> LLMChain: 18 | """Get the response parser.""" 19 | task_creation_template = ( 20 | "You are an task creation AI that uses the result of an execution agent" 21 | " to create new tasks with the following objective: {objective}," 22 | " The last completed task has the result: {result}." 23 | " This result was based on this task description: {task_description}." 24 | " These are incomplete tasks: {incomplete_tasks}." 25 | " Based on the result, create new tasks to be completed" 26 | " by the AI system that do not overlap with incomplete tasks." 27 | " Return the tasks as an array." 28 | ) 29 | prompt = PromptTemplate( 30 | template=task_creation_template, 31 | partial_variables={"objective": objective}, 32 | input_variables=["result", "task_description", "incomplete_tasks"], 33 | ) 34 | return cls(prompt=prompt, llm=llm, verbose=verbose) 35 | 36 | def get_next_task( 37 | self, result: Dict, task_description: str, task_list: List[str] 38 | ) -> List[Dict]: 39 | """Get the next task.""" 40 | incomplete_tasks = ", ".join(task_list) 41 | response = self.run( 42 | result=result, 43 | task_description=task_description, 44 | incomplete_tasks=incomplete_tasks, 45 | ) 46 | new_tasks = response.split("\n") 47 | return [ 48 | {"task_name": task_name} for task_name in new_tasks if task_name.strip() 49 | ] 50 | 51 | 52 | class TaskPrioritizationChain(LLMChain): 53 | """Chain to prioritize tasks.""" 54 | 55 | @classmethod 56 | def from_llm(cls, llm: BaseLLM, objective: str, verbose: bool = True) -> LLMChain: 57 | """Get the response parser.""" 58 | task_prioritization_template = ( 59 | "You are an task prioritization AI tasked with cleaning the formatting of and reprioritizing" 60 | " the following tasks: {task_names}." 61 | " Consider the ultimate objective of your team: {objective}." 62 | " Do not remove any tasks. Return the result as a numbered list, like:" 63 | " #. First task" 64 | " #. Second task" 65 | " Start the task list with number {next_task_id}." 66 | ) 67 | prompt = PromptTemplate( 68 | template=task_prioritization_template, 69 | partial_variables={"objective": objective}, 70 | input_variables=["task_names", "next_task_id"], 71 | ) 72 | return cls(prompt=prompt, llm=llm, verbose=verbose) 73 | 74 | def prioritize_tasks(self, this_task_id: int, task_list: List[Dict]) -> List[Dict]: 75 | """Prioritize tasks.""" 76 | task_names = [t["task_name"] for t in task_list] 77 | next_task_id = int(this_task_id) + 1 78 | response = self.run(task_names=task_names, next_task_id=next_task_id) 79 | new_tasks = response.split("\n") 80 | prioritized_task_list = [] 81 | for task_string in new_tasks: 82 | if not task_string.strip(): 83 | continue 84 | task_parts = task_string.strip().split(".", 1) 85 | if len(task_parts) == 2: 86 | task_id = task_parts[0].strip() 87 | task_name = task_parts[1].strip() 88 | prioritized_task_list.append( 89 | {"task_id": task_id, "task_name": task_name} 90 | ) 91 | return prioritized_task_list 92 | 93 | 94 | class ExecutionChain(LLMChain): 95 | """Chain to execute tasks.""" 96 | 97 | vectorstore: VectorStore = Field(init=False) 98 | 99 | @classmethod 100 | def from_llm( 101 | cls, llm: BaseLLM, vectorstore: VectorStore, verbose: bool = True 102 | ) -> LLMChain: 103 | """Get the response parser.""" 104 | execution_template = ( 105 | "You are an AI who performs one task based on the following objective: {objective}." 106 | " Take into account these previously completed tasks: {context}." 107 | " Your task: {task}." 108 | " Response:" 109 | ) 110 | prompt = PromptTemplate( 111 | template=execution_template, 112 | input_variables=["objective", "context", "task"], 113 | ) 114 | return cls(prompt=prompt, llm=llm, verbose=verbose, vectorstore=vectorstore) 115 | 116 | def _get_top_tasks(self, query: str, k: int) -> List[str]: 117 | """Get the top k tasks based on the query.""" 118 | results = self.vectorstore.similarity_search_with_score(query, k=k) 119 | if not results: 120 | return [] 121 | sorted_results, _ = zip(*sorted(results, key=lambda x: x[1], reverse=True)) 122 | return [str(item.metadata["task"]) for item in sorted_results] 123 | 124 | def execute_task(self, objective: str, task: str, k: int = 5) -> str: 125 | """Execute a task.""" 126 | context = self._get_top_tasks(query=objective, k=k) 127 | return self.run(objective=objective, context=context, task=task) 128 | 129 | 130 | class BabyAGI(BaseModel): 131 | """Controller model for the BabyAGI agent.""" 132 | 133 | objective: str = Field(alias="objective") 134 | task_list: deque = Field(default_factory=deque) 135 | task_creation_chain: TaskCreationChain = Field(...) 136 | task_prioritization_chain: TaskPrioritizationChain = Field(...) 137 | execution_chain: ExecutionChain = Field(...) 138 | task_id_counter: int = Field(1) 139 | 140 | def add_task(self, task: Dict): 141 | self.task_list.append(task) 142 | 143 | def print_task_list(self): 144 | st.text("Task List ⏰") 145 | for t in self.task_list: 146 | st.write("- " + str(t["task_id"]) + ": " + t["task_name"]) 147 | 148 | def print_next_task(self, task: Dict): 149 | st.subheader("Next Task:") 150 | st.warning("- " + str(task["task_id"]) + ": " + task["task_name"]) 151 | 152 | def print_task_result(self, result: str): 153 | st.subheader("Task Result") 154 | st.info(result, icon="ℹ️") 155 | 156 | def print_task_ending(self): 157 | st.success("Tasks terminated.", icon="✅") 158 | 159 | def run(self, max_iterations: Optional[int] = None): 160 | """Run the agent.""" 161 | num_iters = 0 162 | while True: 163 | if self.task_list: 164 | self.print_task_list() 165 | 166 | # Step 1: Pull the first task 167 | task = self.task_list.popleft() 168 | self.print_next_task(task) 169 | 170 | # Step 2: Execute the task 171 | result = self.execution_chain.execute_task( 172 | self.objective, task["task_name"] 173 | ) 174 | this_task_id = int(task["task_id"]) 175 | self.print_task_result(result) 176 | 177 | # Step 3: Store the result in Pinecone 178 | result_id = f"result_{task['task_id']}" 179 | self.execution_chain.vectorstore.add_texts( 180 | texts=[result], 181 | metadatas=[{"task": task["task_name"]}], 182 | ids=[result_id], 183 | ) 184 | 185 | # Step 4: Create new tasks and reprioritize task list 186 | new_tasks = self.task_creation_chain.get_next_task( 187 | result, task["task_name"], [t["task_name"] for t in self.task_list] 188 | ) 189 | for new_task in new_tasks: 190 | self.task_id_counter += 1 191 | new_task.update({"task_id": self.task_id_counter}) 192 | self.add_task(new_task) 193 | self.task_list = deque( 194 | self.task_prioritization_chain.prioritize_tasks( 195 | this_task_id, list(self.task_list) 196 | ) 197 | ) 198 | num_iters += 1 199 | if max_iterations is not None and num_iters == max_iterations: 200 | self.print_task_ending() 201 | break 202 | 203 | @classmethod 204 | def from_llm_and_objectives( 205 | cls, 206 | llm: BaseLLM, 207 | vectorstore: VectorStore, 208 | objective: str, 209 | first_task: str, 210 | verbose: bool = False, 211 | ) -> "BabyAGI": 212 | """Initialize the BabyAGI Controller.""" 213 | task_creation_chain = TaskCreationChain.from_llm( 214 | llm, objective, verbose=verbose 215 | ) 216 | task_prioritization_chain = TaskPrioritizationChain.from_llm( 217 | llm, objective, verbose=verbose 218 | ) 219 | execution_chain = ExecutionChain.from_llm(llm, vectorstore, verbose=verbose) 220 | controller = cls( 221 | objective=objective, 222 | task_creation_chain=task_creation_chain, 223 | task_prioritization_chain=task_prioritization_chain, 224 | execution_chain=execution_chain, 225 | ) 226 | controller.add_task({"task_id": 1, "task_name": first_task}) 227 | return controller 228 | 229 | 230 | def initial_embeddings(openai_api_key, first_task): 231 | with st.spinner("Initial Embeddings ... "): 232 | # Define your embedding model 233 | embeddings = OpenAIEmbeddings( 234 | openai_api_key=openai_api_key, model="text-embedding-ada-002" 235 | ) 236 | 237 | vectorstore = FAISS.from_texts( 238 | ["_"], embeddings, metadatas=[{"task": first_task}] 239 | ) 240 | return vectorstore 241 | 242 | 243 | def main(): 244 | st.title("👶🏼 Baby-AGI 🤖 ") 245 | st.markdown( 246 | """ 247 | > Powerd by : 🦜 [LangChain](https://python.langchain.com/en/latest/use_cases/agents/baby_agi.html) + [Databutton](https://www.databutton.io) 💜 248 | """ 249 | ) 250 | # with st.sidebar: 251 | openai_api_key = st.text_input( 252 | "🔑 :orange[Insert Your OpenAI API KEY]:", 253 | type="password", 254 | placeholder="sk-", 255 | key="pass", 256 | ) 257 | if openai_api_key: 258 | OBJECTIVE = st.text_input( 259 | label=" 🏁 :orange[What's Your Ultimate Goal]: ", 260 | value="Learn Python in 3 days", 261 | ) 262 | 263 | first_task = st.text_input( 264 | label=" 🥇:range[WInitial task:] ", 265 | value="Make a todo list", 266 | ) 267 | # first_task = "Make a todo list" 268 | 269 | max_iterations = st.number_input( 270 | " 💫 :orange[Max Iterations]: ", value=3, min_value=1, step=1 271 | ) 272 | 273 | vectorstore = initial_embeddings(openai_api_key, first_task) 274 | 275 | if st.button(" 🪄 Let me perform the magic 👼🏼"): 276 | try: 277 | baby_agi = BabyAGI.from_llm_and_objectives( 278 | llm=OpenAI(openai_api_key=openai_api_key), 279 | vectorstore=vectorstore, 280 | objective=OBJECTIVE, 281 | first_task=first_task, 282 | verbose=False, 283 | ) 284 | with st.spinner("👶 BabyAGI 🤖 at work ..."): 285 | baby_agi.run(max_iterations=max_iterations) 286 | 287 | st.balloons() 288 | except Exception as e: 289 | st.error(e) 290 | else: 291 | st.warning( 292 | "OpenAI API KEY is necessary to get started." 293 | ) 294 | 295 | 296 | if __name__ == "__main__": 297 | main() 298 | 299 | # Written at 300 | --------------------------------------------------------------------------------