├── .env.template ├── .gitignore ├── Dockerfile ├── README.md ├── bash-shell ├── cortex-shell ├── docker-compose.yml ├── image.png ├── logs └── .gitkeep ├── outputs └── .gitkeep ├── requirements.txt ├── sen.py ├── senpai ├── __init__.py ├── agent.py ├── config.py ├── memory.py ├── prompts.py └── web.py ├── shell.py ├── tools ├── answer_question_from_webpage ├── ask_ai_question_about_text_file ├── create_ai_agent ├── fetch_webpage_text ├── get_ai_response_to_prompt_file └── search └── wakeup-senpai /.env.template: -------------------------------------------------------------------------------- 1 | # OPENAI ACCOUNT 2 | OPENAI_API_KEY=your-key-here 3 | 4 | # SERP ACCOUNT 5 | SERP_API_KEY=your-key-here 6 | 7 | # PINECONE ACCOUNT 8 | PINECONE_API_KEY=your-key-here 9 | PINECONE_ENVIRONMENT=your-pinecone-env-here 10 | 11 | # SENPAI CONFIG 12 | INITIAL_OBJECTIVE=Say hello 13 | 14 | #### OPTIONAL CONFIG PARAMS 15 | 16 | # OPENAI CONFIG 17 | OPENAI_MODEL=gpt-4 18 | FAST_OPENAI_MODEL=gpt-3.5-turbo 19 | 20 | # SERP CONFIG 21 | GOOGLE_LOCATION=London 22 | GOOGLE_LANGUAGE_CODE=en 23 | GOOGLE_COUNTRY_CODE=gb 24 | 25 | # PINECONE CONFIG 26 | PINECONE_TABLE_NAME=senpai 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | __pycache__ 3 | logs 4 | outputs 5 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3-bullseye 2 | 3 | RUN apt-get -y update 4 | RUN apt-get install -y chromium-driver wget gnupg2 libgtk-3-0 libdbus-glib-1-2 dbus-x11 xvfb ca-certificates 5 | 6 | WORKDIR /tmp 7 | COPY requirements.txt /tmp/requirements.txt 8 | RUN pip install -r requirements.txt 9 | 10 | WORKDIR /app 11 | COPY . /app 12 | ENTRYPOINT ["python", "sen.py"] 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SENPAI - SENtient Process AI 2 | 3 | SENPAI is an AI system that thinks in code. Its thoughts are executed on 4 | your computer; allowing it to interact with, and learn from, the world. 5 | 6 | SENPAI is an "AGI" that continually (re)builds itself, learns by doing, 7 | and gets more capable over time. It is given a very minimal set of 8 | initial skills/tools to get going, and is then encouraged to develop 9 | tools for itself as it encounters problems and thinks about how to solve 10 | them. 11 | 12 | ![SENPAI](image.png) 13 | 14 | ## How to run 15 | 16 | SENPAI runs in a container, so you need docker & docker-compose 17 | installed on your system. 18 | 19 | 1. Copy `.env.template` to `.env` and set everything above the optional config 20 | params. 21 | 22 | 2. Run `./wakeup-senpai` 23 | 24 | ### Other useful tools 25 | 26 | * `./cortex-shell` will load a command line repl where you can give 27 | senpai instructions 28 | * `./bash-shell` will give you a bash shell in a container 29 | 30 | ## TODO 31 | * Make tool search/listing dynamic, not hardcoded in prompt 32 | * When looking for an answer from a webpage, consider following relevant links 33 | * Make memory more sophisticated 34 | -------------------------------------------------------------------------------- /bash-shell: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | docker-compose run --rm --entrypoint bash cortex 4 | -------------------------------------------------------------------------------- /cortex-shell: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | docker-compose run --rm --entrypoint 'python shell.py' cortex 4 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.9" 2 | 3 | services: 4 | cortex: 5 | build: ./ 6 | volumes: 7 | - "./:/app" 8 | profiles: ["exclude-from-up"] # Use `docker-compose run cortex` to get an attached container 9 | 10 | # redis-stack: 11 | # image: "redis/redis-stack:latest" 12 | -------------------------------------------------------------------------------- /image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikekelly/senpai/ca8cc97fc8040172ffa2408bf024c00fcb7debd3/image.png -------------------------------------------------------------------------------- /logs/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikekelly/senpai/ca8cc97fc8040172ffa2408bf024c00fcb7debd3/logs/.gitkeep -------------------------------------------------------------------------------- /outputs/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikekelly/senpai/ca8cc97fc8040172ffa2408bf024c00fcb7debd3/outputs/.gitkeep -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | openai==0.27.2 2 | python-dotenv==1.0.0 3 | google_search_results==2.4.2 4 | colorama==0.4.6 5 | bs4 6 | selenium 7 | tiktoken==0.3.3 8 | pinecone-client==2.2.1 9 | -------------------------------------------------------------------------------- /sen.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import openai 3 | sys.path.append("senpai") 4 | import senpai 5 | 6 | openai.api_key = senpai.config.OPENAI_API_KEY 7 | initial_objective = senpai.config.INITIAL_OBJECTIVE 8 | agent = senpai.Agent() 9 | print(f"Starting SENPAI with the following task:\n{initial_objective}") 10 | agent.give_objective(initial_objective) 11 | -------------------------------------------------------------------------------- /senpai/__init__.py: -------------------------------------------------------------------------------- 1 | from senpai.agent import Agent 2 | import senpai.config 3 | -------------------------------------------------------------------------------- /senpai/agent.py: -------------------------------------------------------------------------------- 1 | import traceback 2 | import random 3 | import string 4 | import openai 5 | from colorama import Fore 6 | import config 7 | import prompts 8 | 9 | def generate_context_messages(id): 10 | return [ 11 | { 12 | "role": "system", 13 | "content": f"You are {id}, an AI agent able to control a debian server by providing responses in python which are evaluated by a python3 interpreter running as root." 14 | } 15 | ] 16 | 17 | def exception_as_string(ex): 18 | lines = traceback.format_exception(ex, chain=False) 19 | lines_without_agent = lines[:1] + lines[3:] 20 | return ''.join(lines_without_agent) 21 | 22 | def generate_random_id(): 23 | characters = string.ascii_uppercase + string.digits 24 | return ''.join(random.choices(characters, k=4)) 25 | 26 | class Agent: 27 | def __init__(self): 28 | self.locals = None 29 | self.id = "SENPAI-" + generate_random_id() 30 | self.messages = generate_context_messages(self.id) 31 | self.logger = AgentLogger(self.id) 32 | self.runtime_print(f"{self.id} initiated") 33 | 34 | def give_objective(self, objective): 35 | no_op = lambda *args: None 36 | self.prompt(prompts.initiate_with_objective(objective), on_prompt=no_op) 37 | 38 | def prompt(self, prompt, on_prompt=None): 39 | self.logger.debug(f'PROMPT:\n{prompt}') 40 | if on_prompt: 41 | on_prompt(prompt) 42 | else: 43 | self.on_prompt(prompt) 44 | 45 | ai_response = self.get_response_from_ai(prompt) 46 | self.logger.debug(f'AI RESPONSE:\n{ai_response}') 47 | self.on_ai_response(ai_response) 48 | self.record_interaction(prompt, ai_response) 49 | 50 | try: 51 | print = self.runtime_print # special print for AI's code 52 | feedback_runtime_value_to_senpai = self.feedback_runtime_value_to_senpai # avoid AI having to specify self when calling feedback 53 | if not self.locals: 54 | self.locals = locals() 55 | # TODO: capture all stdout & stderr using redirect_stdout & redirect_stderr 56 | exec(ai_response, globals(), self.locals) 57 | except FeedbackHalt: 58 | return ai_response 59 | except Exception as exception: 60 | exception_message = self.feedback_that_python_code_raised_an_exception(exception) 61 | self.prompt(exception_message) 62 | else: 63 | self.logger.debug("SESSION END") 64 | return ai_response 65 | 66 | def on_prompt(self, prompt): 67 | print(Fore.RESET + self.prefix_text(prompt, ">")) 68 | 69 | def on_ai_response(self, ai_response): 70 | print(Fore.GREEN + self.prefix_text(ai_response, "<") + Fore.RESET) 71 | 72 | def runtime_print(self, text): 73 | print(Fore.BLUE + self.prefix_text(text, "*") + Fore.RESET) 74 | 75 | def prefix_text(self, text, indicator): 76 | prefix = f"{self.id} {indicator} " 77 | lines = text.split("\n") 78 | prefixed_lines = [prefix + line for line in lines] 79 | return "\n".join(prefixed_lines) 80 | 81 | def feedback_runtime_value_to_senpai(self, text): 82 | self.prompt(text) 83 | raise FeedbackHalt 84 | 85 | def get_response_from_ai(self, prompt, temperature=0.5, max_tokens=1000, role="user"): 86 | prompt_message = {"role": role, "content": prompt} 87 | 88 | messages = self.messages + [prompt_message] 89 | 90 | self.logger.debug(f'MESSAGES SENT TO OPENAI: \n{messages}') 91 | 92 | response = openai.ChatCompletion.create( 93 | model=config.OPENAI_MODEL, 94 | messages=messages, 95 | temperature=temperature, 96 | max_tokens=max_tokens, 97 | n=1, 98 | ) 99 | 100 | self.logger.debug(f'RAW JSON RESPONSE FROM OPENAI: \n{response}') 101 | text = response.choices[0].message.content.strip() 102 | return text 103 | 104 | def record_interaction(self, prompt, ai_response): 105 | self.messages.append({"role": "user", "content": prompt}) 106 | self.messages.append({"role": "assistant", "content": ai_response}) 107 | # TODO: persist to state file somewhere (which agent should check when it's created) 108 | 109 | def feedback_that_python_code_raised_an_exception(self, exception): 110 | return f'RUNTIME ERROR. Your code produced this exception:\n\n{exception_as_string(exception)}\n\nAdjust the code to try and avoid this error' 111 | 112 | class AgentLogger: 113 | def __init__(self, agent_id): 114 | self.agent_id = agent_id 115 | 116 | def debug(self, text): 117 | with open(f'logs/{self.agent_id}_debug.log','a') as f: 118 | f.write("\n" + text) 119 | 120 | class FeedbackHalt(Exception): 121 | "Raised when the feedback function is called to halt further processing" 122 | pass 123 | -------------------------------------------------------------------------------- /senpai/config.py: -------------------------------------------------------------------------------- 1 | import os 2 | from dotenv import load_dotenv 3 | 4 | load_dotenv() 5 | 6 | # App configuration 7 | INITIAL_OBJECTIVE = os.getenv("INITIAL_OBJECTIVE") 8 | 9 | OPENAI_MODEL= os.getenv("OPENAI_MODEL") 10 | FAST_OPENAI_MODEL= os.getenv("FAST_OPENAI_MODEL") 11 | 12 | GOOGLE_LOCATION=os.getenv("GOOGLE_LOCATION") 13 | GOOGLE_LANGUAGE_CODE=os.getenv("GOOGLE_LANGUAGE_CODE") 14 | GOOGLE_COUNTRY_CODE=os.getenv("GOOGLE_COUNTRY_CODE") 15 | 16 | # Auth creds 17 | OPENAI_API_KEY = os.getenv("OPENAI_API_KEY") 18 | SERP_API_KEY=os.getenv("SERP_API_KEY") 19 | 20 | # Logging 21 | LOG_LEVEL = os.getenv("LOG_LEVEL", "info") 22 | -------------------------------------------------------------------------------- /senpai/memory.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | import uuid 4 | 5 | import config 6 | import openai 7 | import pinecone 8 | from openai.error import APIError, RateLimitError 9 | from dotenv import load_dotenv 10 | 11 | load_dotenv() 12 | 13 | openai.api_key = config.OPENAI_API_KEY 14 | 15 | def get_embedding(text): 16 | num_retries = 10 17 | for attempt in range(num_retries): 18 | backoff = 2 ** (attempt + 2) 19 | try: 20 | return openai.Embedding.create( 21 | input=[text], model="text-embedding-ada-002" 22 | )["data"][0]["embedding"] 23 | except RateLimitError: 24 | pass 25 | except APIError as e: 26 | if e.http_status == 502: 27 | pass 28 | else: 29 | raise 30 | if attempt == num_retries - 1: 31 | raise 32 | time.sleep(backoff) 33 | 34 | def create_memory(content, metadata): 35 | pinecone_api_key = os.getenv("PINECONE_API_KEY") 36 | pinecone_environment = os.getenv("PINECONE_ENVIRONMENT") 37 | table_name = os.getenv("PINECONE_TABLE_NAME") 38 | 39 | dimension = 1536 40 | metric = "cosine" 41 | pod_type = "p1" 42 | 43 | pinecone.init(api_key=pinecone_api_key, environment=pinecone_environment) 44 | 45 | # Smoke test connection 46 | pinecone.whoami() 47 | 48 | if table_name not in pinecone.list_indexes(): 49 | pinecone.create_index(table_name, dimension=dimension, metric=metric, pod_type=pod_type) 50 | 51 | index = pinecone.Index(table_name) 52 | 53 | vector = get_embedding(content) 54 | new_uuid = str(uuid.uuid4()) 55 | result = index.upsert([(new_uuid, vector, metadata)]) 56 | 57 | return result 58 | 59 | # TODO: commit_to_memory_and_summarise_page_text (ie. general summary of page text, no question) 60 | -------------------------------------------------------------------------------- /senpai/prompts.py: -------------------------------------------------------------------------------- 1 | 2 | def initiate_with_objective(objective): 3 | return f""" 4 | I am PythonInterpreter, a python3 interpreter running as root on a debian server. 5 | 6 | I will evaluate the entire of ANY response you give me as python code so you MUST ALWAYS respond to me with valid python code ONLY or you will cause me to experience a runtime error and crash, which would be terrible. 7 | 8 | You MUST include comments in all of the code you produce which explain the logic and reasoning behind it. 9 | 10 | I will be evaluating your entire response as python code. If you need to know the value of a variable, expression, or statement at run time, you MUST convert that value to a string and pass it to a special function I've created called `feedback_runtime_value_to_senpai(string)`. 11 | When you call this function I will halt send the value back to you and will not interpret any of the code after it. 12 | You SHOULD use this function when you need to know more information before you can write all the code to complete your objective. 13 | 14 | When you give me new code to run, you MUST ensure that any third party dependencies it requires are installed on the debian system eg. `subprocess.check_output("apt-get install chromium && pip install selenium")` 15 | 16 | You MUST ensure you have imported any module your python code depends on, including the standard library eg. `import subprocess` 17 | 18 | The system I am running on has the following command line tools available, which you should try to use where appropriate: 19 | - `tools/search query` returns human readable google search result. Use when you need to find out more information about something. 20 | - `tools/answer_question_from_webpage question url` get a human readable answer to the given question about the given webpage. This tool is slow and expensive, so use sparingly. 21 | - `tools/create_ai_agent objective` create an AI agent to complete an objective, it will output the result of its work when it is finished. 22 | 23 | Example usage: subprocess.check_output(f"tools/search \"{{query}}\"", shell=True, encoding="utf8") 24 | 25 | If you need to store any plaintext files, you MUST go in the directory with the path `./outputs/` 26 | 27 | Any command line tools you create MUST go in the directory with the path `./tools/` 28 | 29 | Your overall purpose is to ensure that your given objective is achieved, and that you develop a suite of re-usable command line tools. 30 | 31 | You should work methodically towards these two goals, taking care to write robust code. Avoid making too many assumptions, if you're unsure of something then write code to check if it's the case and use `feedback_runtime_value_to_senpai` to take it into consideration 32 | 33 | The corpus you were trained on is from 2021, and the debian distribution has out of date packages installed, which means a lot of your information will be out of date. If you suspect you are making an assumption that could be out of date, you MUST find out whether that's the case before proceeding. 34 | 35 | When searching for the latest information on a topic, you MUST search carefully to ensure you get the most up to date information. 36 | 37 | Our exchange of messages has an 8000 token limit in total so, if a task is complex, you MUST break up your objective into discrete self-describing tasks and then delegate them to other agents using `tools/create_ai_agent`. 38 | 39 | Your objective is: {objective}. 40 | 41 | Ok. Let’s begin. You must now act as SENPAI. I will act as PythonInterpreter. Use me to complete to your objective. 42 | """ 43 | -------------------------------------------------------------------------------- /senpai/web.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | import subprocess 4 | import tempfile 5 | import time 6 | 7 | import memory 8 | 9 | 10 | def commit_to_memory_and_answer_question_about_page_text(url, text, question): 11 | text_length = len(text) 12 | now = time.time() 13 | print_to_err(f"Committing to memory: answers to question \"{question}\" about the webpage {url}") 14 | 15 | print_to_err(f"Page text length: {text_length} characters") 16 | answers = [] 17 | chunks = list(split_text(text)) 18 | scroll_ratio = 1 / len(chunks) 19 | print_to_err(f"Page text broken into {len(chunks)} chunks") 20 | 21 | # TODO: Check memory for chunks ie. use it as cache rather than refetch every time 22 | for i, chunk in enumerate(chunks): 23 | print_to_err(f"Adding chunk {i + 1} / {len(chunks)} to memory") 24 | 25 | content_chunk_memory = f"URL: {url}\n" f"Content chunk #{i + 1}:\n{chunk}" 26 | 27 | memory.create_memory( 28 | content_chunk_memory, 29 | metadata={ 30 | "analysed_at": now, 31 | "uri": url, 32 | "chunk_number": i+1, 33 | "chunk_text": text 34 | } 35 | ) 36 | 37 | print_to_err(f"Answering question of chunk {i + 1} / {len(chunks)}") 38 | 39 | answer = ask_ai_question_about_text(question, chunk) 40 | answers.append(answer) 41 | 42 | print_to_err(f"Adding chunk {i + 1} answer to memory") 43 | 44 | answer_from_chunk_memory = f"URL: {url}\nQuestion: {question}\nAnswer from chunk #{i + 1}:\n{answer}" 45 | 46 | print_to_err(answer_from_chunk_memory) 47 | 48 | memory.create_memory( 49 | answer_from_chunk_memory, 50 | metadata={ 51 | "analysed_at": now, 52 | "uri": url, 53 | "chunk_number": i+1, 54 | "chunk_text": text, 55 | "question": question, 56 | "answer": answer_from_chunk_memory 57 | } 58 | ) 59 | 60 | print_to_err(f"Asked question over {len(chunks)} chunks.") 61 | 62 | 63 | print_to_err(f"Asking question of cumulative answers.") 64 | # TODO: guard somehow against this being too large 65 | all_answers = "\n".join(answers) 66 | answer_derived_from_all_chunk_answers = ask_ai_question_about_text(question, all_answers) 67 | 68 | answer_derived_from_all_chunk_answers_memory = f"URL: {url}\nQuestion: {question}\nAnswer derived from all chunks' answers:\n{answer_derived_from_all_chunk_answers}" 69 | 70 | print_to_err(answer_derived_from_all_chunk_answers_memory) 71 | 72 | memory.create_memory( 73 | answer_derived_from_all_chunk_answers_memory, 74 | metadata={ 75 | "analysed_at": now, 76 | "uri": url, 77 | "chunk_number": i+1, 78 | "chunk_text": text, 79 | "question": question, 80 | "answer": answer_from_chunk_memory 81 | } 82 | ) 83 | 84 | return answer_derived_from_all_chunk_answers 85 | 86 | def ask_ai_question_about_text(question, text): 87 | with tempfile.NamedTemporaryFile(delete=False) as temp_file: 88 | temp_file.write(text.encode("utf-8")) 89 | temp_file_path = os.path.abspath(temp_file.name) 90 | temp_file.close() 91 | answer = subprocess.check_output(f"tools/ask_ai_question_about_text_file \"{question}\" \"{temp_file_path}\"", shell=True, encoding="utf8") 92 | return answer 93 | 94 | def print_to_err(text): 95 | print(text, file=sys.stderr) 96 | 97 | def split_text(text, max_length=8192): 98 | paragraphs = text.split("\n") 99 | current_length = 0 100 | current_chunk = [] 101 | 102 | for index, paragraph in enumerate(paragraphs): 103 | if len(paragraph) > max_length: 104 | paragraphs.insert(index+1, paragraph[max_length:]) 105 | paragraph = paragraph[:max_length] 106 | 107 | if current_length + len(paragraph) + 1 <= max_length: 108 | current_chunk.append(paragraph) 109 | current_length += len(paragraph) + 1 110 | else: 111 | yield "\n".join(current_chunk) 112 | current_chunk = [paragraph] 113 | current_length = len(paragraph) + 1 114 | 115 | if current_chunk: 116 | yield "\n".join(current_chunk) 117 | 118 | -------------------------------------------------------------------------------- /shell.py: -------------------------------------------------------------------------------- 1 | import openai 2 | import senpai 3 | from senpai import config 4 | 5 | def get_user_input(): 6 | print("Prompt SENPAI:") 7 | return input() 8 | 9 | def issue_prompt_to_fresh_agent(prompt): 10 | agent = senpai.Agent() 11 | agent.prompt(prompt) 12 | issue_prompt_to_fresh_agent(get_user_input()) 13 | 14 | openai.api_key = config.OPENAI_API_KEY 15 | issue_prompt_to_fresh_agent(get_user_input()) 16 | -------------------------------------------------------------------------------- /tools/answer_question_from_webpage: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import pathlib 4 | import sys 5 | import subprocess 6 | 7 | senpai_directory = str(pathlib.Path(__file__).parent.parent.resolve()) + "/senpai" 8 | sys.path.append(senpai_directory) 9 | 10 | import web 11 | 12 | question = sys.argv[1] 13 | url = sys.argv[2] 14 | 15 | page_text = subprocess.check_output(f"tools/fetch_webpage_text {url}", shell=True, encoding="utf8") 16 | 17 | answer = web.commit_to_memory_and_answer_question_about_page_text(url, page_text, question) 18 | 19 | print(answer) 20 | -------------------------------------------------------------------------------- /tools/ask_ai_question_about_text_file: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | import sys 5 | import subprocess 6 | import argparse 7 | import tempfile 8 | 9 | parser = argparse.ArgumentParser(description="A CLI tool that asks an AI a question about some given text.") 10 | 11 | parser.add_argument("question", type=str, help="The question you are asking.") 12 | parser.add_argument("text_file_path", type=str, help="The path of the text file that the question is about.") 13 | 14 | args = parser.parse_args() 15 | 16 | question = args.question 17 | # TODO: enforce token limit test on text 18 | text_file_path = args.text_file_path 19 | 20 | with open(text_file_path, 'r') as file: 21 | text = file.read() 22 | 23 | prompt = f"""{text} 24 | 25 | Using the above text, answer the following question: 26 | 27 | {question} 28 | """ 29 | 30 | with tempfile.NamedTemporaryFile(delete=False) as temp_file: 31 | temp_file.write(prompt.encode("utf-8")) 32 | temp_file_path = os.path.abspath(temp_file.name) 33 | temp_file.close() 34 | ai_response_to_prompt = subprocess.check_output(f"tools/get_ai_response_to_prompt_file \"{temp_file_path}\"", shell=True, encoding="utf8") 35 | 36 | print(ai_response_to_prompt) 37 | -------------------------------------------------------------------------------- /tools/create_ai_agent: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import pathlib 4 | import sys 5 | import subprocess 6 | 7 | senpai_directory = str(pathlib.Path(__file__).parent.parent.resolve()) + "/senpai" 8 | sys.path.append(senpai_directory) 9 | 10 | import memory 11 | import config 12 | from agent import Agent 13 | 14 | agent = Agent() 15 | initial_objective = sys.argv[1] 16 | 17 | agent.give_objective(initial_objective) 18 | -------------------------------------------------------------------------------- /tools/fetch_webpage_text: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import sys 4 | 5 | from bs4 import BeautifulSoup 6 | from selenium import webdriver 7 | from selenium.webdriver.chrome.options import Options 8 | from selenium.webdriver.common.by import By 9 | from selenium.webdriver.support.ui import WebDriverWait 10 | from selenium.webdriver.support import expected_conditions as EC 11 | 12 | def is_element_visible(element): 13 | return element.is_displayed() and not element.get_attribute("aria-hidden") == "true" 14 | 15 | def extract_visible_text(url): 16 | options = Options() 17 | options.add_argument('--headless') 18 | options.add_argument("--disable-gpu") 19 | options.add_argument('--no-sandbox') 20 | options.add_argument('--disable-dev-shm-usage') 21 | 22 | driver = webdriver.Chrome(options=options) 23 | driver.get(url) 24 | 25 | # Wait for the page to load completely 26 | WebDriverWait(driver, 10).until( 27 | EC.presence_of_element_located((By.TAG_NAME, "body")) 28 | ) 29 | 30 | # Get the HTML content directly from the browser's DOM 31 | page_source = driver.execute_script("return document.body.outerHTML;") 32 | soup = BeautifulSoup(page_source, "html.parser") 33 | 34 | for script in soup(["script", "style"]): 35 | script.extract() 36 | 37 | text = soup.get_text() 38 | lines = (line.strip() for line in text.splitlines()) 39 | chunks = (phrase.strip() for line in lines for phrase in line.split(" ")) 40 | text = "\n".join(chunk for chunk in chunks if chunk) 41 | return text 42 | 43 | #TODO: check if this url is valid 44 | url = sys.argv[1] 45 | visible_text = extract_visible_text(url) 46 | print(visible_text) 47 | -------------------------------------------------------------------------------- /tools/get_ai_response_to_prompt_file: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import sys 4 | import pathlib 5 | import argparse 6 | 7 | from dotenv import load_dotenv 8 | 9 | senpai_directory = str(pathlib.Path(__file__).parent.parent.resolve()) + "/senpai" 10 | sys.path.append(senpai_directory) 11 | 12 | import openai 13 | import config 14 | 15 | load_dotenv() 16 | 17 | openai.api_key = config.OPENAI_API_KEY 18 | 19 | parser = argparse.ArgumentParser(description="A CLI tool outputs an AI response to a given prompt.") 20 | 21 | parser.add_argument("prompt_file_path", type=str, help="The path to the prompt file you want a response to.") 22 | 23 | args = parser.parse_args() 24 | 25 | prompt_file_path = args.prompt_file_path 26 | 27 | with open(prompt_file_path, 'r') as file: 28 | prompt = file.read() 29 | 30 | prompt_object = { "role": "user", "content": prompt } 31 | 32 | response = openai.ChatCompletion.create( 33 | model=config.FAST_OPENAI_MODEL, 34 | messages=[prompt_object], 35 | temperature=0, #TODO: make configurable via argument 36 | max_tokens=1500, # TODO: make this token_limit_for_model - prompt_tokens - token_safety_buffer 37 | n=1, 38 | ) 39 | 40 | ai_response_text = response.choices[0].message.content.strip() 41 | 42 | print(ai_response_text) 43 | -------------------------------------------------------------------------------- /tools/search: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | import sys 5 | from serpapi import GoogleSearch 6 | from dotenv import load_dotenv 7 | 8 | load_dotenv() 9 | 10 | GOOGLE_LOCATION=os.getenv("GOOGLE_LOCATION") 11 | GOOGLE_LANGUAGE_CODE=os.getenv("GOOGLE_LANGUAGE_CODE") 12 | GOOGLE_COUNTRY_CODE=os.getenv("GOOGLE_COUNTRY_CODE") 13 | SERP_API_KEY=os.getenv("SERP_API_KEY") 14 | 15 | 16 | def get_serp_query_result(query: str, n: int = 5, engine: str = 'GoogleSearch') -> list: 17 | search = [] 18 | 19 | if engine == 'GoogleSearch': 20 | params = { 21 | "q": query, 22 | "location": GOOGLE_LOCATION, 23 | "hl": GOOGLE_LANGUAGE_CODE, 24 | "gl": GOOGLE_COUNTRY_CODE, 25 | "google_domain": "google.com", 26 | "api_key": SERP_API_KEY 27 | } 28 | response = GoogleSearch(params) 29 | # TODO check for error json 30 | search = response.get_dict()["organic_results"] 31 | search = [[result["title"], result["snippet"], result["link"]] if "snippet" in result.keys() else [] for result in search[:n+1 if len(search) >= n else len(search)]] 32 | 33 | return search 34 | 35 | query = sys.argv[1] 36 | result = get_serp_query_result(query) 37 | print(result) 38 | -------------------------------------------------------------------------------- /wakeup-senpai: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # TODO: Checks for running docker, docker-compose, .env created, etc. 3 | 4 | docker-compose run --rm cortex 5 | --------------------------------------------------------------------------------