├── .env.example ├── .gitignore ├── README.md ├── alembic.ini ├── alembic ├── README ├── env.py ├── script.py.mako └── versions │ └── d5c1fce30ec3_create_tables.py ├── assets ├── bot.png ├── bot2.png ├── bot3.png └── diagram.png ├── poetry.lock ├── pyproject.toml ├── scripts ├── __init__.py ├── create_all.py └── delete_all_data.py ├── tests ├── __init__.py ├── test_captain.py ├── test_compare_observations.py ├── test_create_autogpt.py ├── test_create_report.py ├── test_scout.py ├── test_send_email.py ├── test_sentinel.py ├── test_soldier.py └── test_team.py └── whenx ├── __init__.py ├── __main__.py ├── console.py ├── database.py ├── models ├── __init__.py ├── captain.py ├── scout.py ├── sentinel.py ├── soldier.py ├── team.py └── user.py └── services ├── __init__.py ├── compare_observations.py ├── create_autogpt.py ├── create_plan_agent.py ├── create_react_agent.py ├── create_report.py ├── create_team.py └── send_email.py /.env.example: -------------------------------------------------------------------------------- 1 | OPENAI_API_KEY= 2 | DATABASE_URL=sqlite:///whenx.db 3 | SERPAPI_API_KEY= 4 | METAPHOR_API_KEY= 5 | RESEND_API_KEY= 6 | SENDER_EMAIL=onboarding@resend.dev 7 | USER_EMAIL= -------------------------------------------------------------------------------- /.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 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 86 | __pypackages__/ 87 | 88 | # Celery stuff 89 | celerybeat-schedule 90 | celerybeat.pid 91 | 92 | # SageMath parsed files 93 | *.sage.py 94 | 95 | # Environments 96 | .env 97 | .venv 98 | env/ 99 | venv/ 100 | ENV/ 101 | env.bak/ 102 | venv.bak/ 103 | 104 | # Spyder project settings 105 | .spyderproject 106 | .spyproject 107 | 108 | # Rope project settings 109 | .ropeproject 110 | 111 | # mkdocs documentation 112 | /site 113 | 114 | # mypy 115 | .mypy_cache/ 116 | .dmypy.json 117 | dmypy.json 118 | 119 | # Pyre type checker 120 | .pyre/ 121 | 122 | # pytype static type analyzer 123 | .pytype/ 124 | 125 | # Cython debug symbols 126 | cython_debug/ 127 | 128 | # SQLite 129 | *.db 130 | 131 | 132 | .DS_Store 133 | 134 | 135 | .env.production 136 | .env.development -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WhenX: Event-Driven Autonomous Agents 2 | 3 | ![bot2](assets/bot2.png) 4 | 5 | The Semantic Web has long intrigued me. The idea of transforming the web into a comprehensible database accessible to artificial intelligence became a foundation for my continued exploration. The notion of creating a smart alert system, a digital sentinel standing guard over the information chaos of the internet, was an application that continuously sparked my curiosity. Imagine a robot that ceaselessly trawls the vast ocean of the web, alerting you to the occurrence of a specific event, say, the release of a new book. This is a dream I have been chasing for years. 6 | 7 | The emergence of autonomous agents like AutoGPT and BabyAGI piqued my interest. These systems represented a promising new direction for AI technology. I decided to experiment with these tools, with the aspiration of realizing my dream of a smart alert system. My excitement, however, was quickly tempered. These systems, while advanced, operate with a focus on a goal and its subsequent division into steps. This is a far cry from the continuous, vigilant monitoring system I envisioned. Much to my disappointment, I found their architecture ill-suited for such a task. You can make it work sometimes but not reliably. 8 | 9 | The shortcomings of existing models lit a spark to build something new. I decided to create my own system, a system of event-driven autonomous agents. I call it WhenX. 10 | 11 | The system's goal is to create alerts in the form of "When X happens, then do Y". In this initial version, we are only supporting email alerts in the form of "When X happens, send me an email report". 12 | 13 | ### **This is an early experimental project with many bugs! I am still working on it.** 14 | 15 | # Architecture 16 | 17 | ![assets/diagram.png](assets/diagram.png) 18 | 19 | **The system is composed by four agents: The Captain, the Scout, the Sentinel, and the Soldier.** 20 | 21 | **The Captain** is the conductor of this orchestra. Given a mission, it is responsible for assembling a team of agents - a Scout, a Sentinel, and a Soldier. 22 | 23 | **The Scout** serves as the explorer, the one who embarks on the quest for relevant information, creating 'Observations' along its journey. These Observations are the essential raw materials that fuel the WhenX system. 24 | 25 | **The Sentinel**, the gatekeeper, scrutinizes the last N Observations made by the Scout. It is a discerning entity, designed to identify changes, no matter how subtle, in the Observation data. When it detects something noteworthy, it triggers an 'Alarm', signaling the Soldier to action. 26 | 27 | **The Soldier**, the executor, springs into action upon receiving Alarms from the Sentinel. It executes instructions based on the alarms, crafting a 'Report' that encapsulates the detected change or event. 28 | 29 | 30 | # Installation 31 | 32 | ```Bash 33 | $ poetry install 34 | ``` 35 | 36 | ```Bash 37 | $ poetry run alembic upgrade head 38 | ``` 39 | 40 | Copy the .env.example file to .env and fill in the values. We are using the [Resend](https://resend.com/) service 41 | to send emails. You can create a free account and use it for testing. 42 | 43 | 44 | # Usage 45 | 46 | The goal of the system is to create alerts in the form of "When X happens" then "Send me a report". In this initial version, we are only supporting email alerts. 47 | 48 | ## Create a new alert 49 | First we need to create a mission. A mission is a description of the alert. For example, "When Haruki Murakami releases a new book". 50 | ```Bash 51 | $ python -m whenx create --mission "when Haruki Murakami releases a new book." 52 | ``` 53 | ## Run the system 54 | The system will start running and will create a new Observation every day. The Observation is a snapshot of the current state of your query. The system will compare the last 2 observations and if it detects a change, it will send an email with the report. 55 | ```Bash 56 | $ python -m whenx monitor 57 | ``` 58 | 59 | ## List all alerts 60 | You can list all alerts and their status. 61 | ```Bash 62 | $ python -m whenx list 63 | ``` 64 | 65 | ## Delete an alert 66 | You can delete an alert by its id. 67 | ```Bash 68 | $ python -m whenx delete --id a5dc910a-4457-4911-8ba3-c7713588e7ff 69 | ``` 70 | -------------------------------------------------------------------------------- /alembic.ini: -------------------------------------------------------------------------------- 1 | [alembic] 2 | script_location = alembic 3 | sqlalchemy.url = sqlite:///whenx.db 4 | 5 | [loggers] 6 | keys = root,sqlalchemy,alembic 7 | 8 | [handlers] 9 | keys = console 10 | 11 | [formatters] 12 | keys = generic 13 | 14 | [logger_root] 15 | level = WARN 16 | handlers = console 17 | qualname = 18 | 19 | [logger_sqlalchemy] 20 | level = WARN 21 | handlers = 22 | qualname = sqlalchemy.engine 23 | 24 | [logger_alembic] 25 | level = INFO 26 | handlers = 27 | qualname = alembic 28 | 29 | [handler_console] 30 | class = StreamHandler 31 | args = (sys.stderr,) 32 | level = NOTSET 33 | formatter = generic 34 | 35 | [formatter_generic] 36 | format = %(levelname)-5.5s [%(name)s] %(message)s 37 | datefmt = %Y-%m-%d %H:%M:%S -------------------------------------------------------------------------------- /alembic/README: -------------------------------------------------------------------------------- 1 | Generic single-database configuration. -------------------------------------------------------------------------------- /alembic/env.py: -------------------------------------------------------------------------------- 1 | from logging.config import fileConfig 2 | 3 | from sqlalchemy import engine_from_config 4 | from sqlalchemy import pool 5 | 6 | from alembic import context 7 | 8 | from sqlalchemy import MetaData 9 | from whenx.database import Base 10 | from whenx.models import * 11 | 12 | # this is the Alembic Config object, which provides 13 | # access to the values within the .ini file in use. 14 | config = context.config 15 | 16 | # Interpret the config file for Python logging. 17 | # This line sets up loggers basically. 18 | if config.config_file_name is not None: 19 | fileConfig(config.config_file_name) 20 | 21 | # add your model's MetaData object here 22 | # for 'autogenerate' support 23 | # from myapp import mymodel 24 | # target_metadata = mymodel.Base.metadata 25 | target_metadata = Base.metadata 26 | 27 | # other values from the config, defined by the needs of env.py, 28 | # can be acquired: 29 | # my_important_option = config.get_main_option("my_important_option") 30 | # ... etc. 31 | 32 | 33 | def run_migrations_offline() -> None: 34 | """Run migrations in 'offline' mode. 35 | 36 | This configures the context with just a URL 37 | and not an Engine, though an Engine is acceptable 38 | here as well. By skipping the Engine creation 39 | we don't even need a DBAPI to be available. 40 | 41 | Calls to context.execute() here emit the given string to the 42 | script output. 43 | 44 | """ 45 | url = config.get_main_option("sqlalchemy.url") 46 | context.configure( 47 | url=url, 48 | target_metadata=target_metadata, 49 | literal_binds=True, 50 | dialect_opts={"paramstyle": "named"}, 51 | ) 52 | 53 | with context.begin_transaction(): 54 | context.run_migrations() 55 | 56 | 57 | def run_migrations_online() -> None: 58 | """Run migrations in 'online' mode. 59 | 60 | In this scenario we need to create an Engine 61 | and associate a connection with the context. 62 | 63 | """ 64 | connectable = engine_from_config( 65 | config.get_section(config.config_ini_section, {}), 66 | prefix="sqlalchemy.", 67 | poolclass=pool.NullPool, 68 | ) 69 | 70 | with connectable.connect() as connection: 71 | context.configure( 72 | connection=connection, target_metadata=target_metadata 73 | ) 74 | 75 | with context.begin_transaction(): 76 | context.run_migrations() 77 | 78 | 79 | if context.is_offline_mode(): 80 | run_migrations_offline() 81 | else: 82 | run_migrations_online() 83 | -------------------------------------------------------------------------------- /alembic/script.py.mako: -------------------------------------------------------------------------------- 1 | """${message} 2 | 3 | Revision ID: ${up_revision} 4 | Revises: ${down_revision | comma,n} 5 | Create Date: ${create_date} 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | ${imports if imports else ""} 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = ${repr(up_revision)} 14 | down_revision = ${repr(down_revision)} 15 | branch_labels = ${repr(branch_labels)} 16 | depends_on = ${repr(depends_on)} 17 | 18 | 19 | def upgrade() -> None: 20 | ${upgrades if upgrades else "pass"} 21 | 22 | 23 | def downgrade() -> None: 24 | ${downgrades if downgrades else "pass"} 25 | -------------------------------------------------------------------------------- /alembic/versions/d5c1fce30ec3_create_tables.py: -------------------------------------------------------------------------------- 1 | """create tables 2 | 3 | Revision ID: d5c1fce30ec3 4 | Revises: 5 | Create Date: 2023-06-14 10:51:40.234862 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = 'd5c1fce30ec3' 14 | down_revision = None 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade() -> None: 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.create_table('users', 22 | sa.Column('id', sa.String(), nullable=False), 23 | sa.Column('name', sa.String(length=50), nullable=True), 24 | sa.Column('email', sa.String(length=120), nullable=True), 25 | sa.PrimaryKeyConstraint('id'), 26 | sa.UniqueConstraint('email') 27 | ) 28 | op.create_table('teams', 29 | sa.Column('id', sa.String(), nullable=False), 30 | sa.Column('name', sa.String(), nullable=True), 31 | sa.Column('description', sa.String(), nullable=True), 32 | sa.Column('userId', sa.String(), nullable=True), 33 | sa.Column('created_at', sa.DateTime(), nullable=True), 34 | sa.Column('updated_at', sa.DateTime(), nullable=True), 35 | sa.ForeignKeyConstraint(['userId'], ['users.id'], ), 36 | sa.PrimaryKeyConstraint('id') 37 | ) 38 | op.create_table('scouts', 39 | sa.Column('id', sa.String(), nullable=False), 40 | sa.Column('instruction', sa.String(), nullable=True), 41 | sa.Column('teamId', sa.String(), nullable=True), 42 | sa.Column('created_at', sa.DateTime(), nullable=True), 43 | sa.Column('updated_at', sa.DateTime(), nullable=True), 44 | sa.ForeignKeyConstraint(['teamId'], ['teams.id'], ), 45 | sa.PrimaryKeyConstraint('id') 46 | ) 47 | op.create_table('sentinels', 48 | sa.Column('id', sa.String(), nullable=False), 49 | sa.Column('instruction', sa.String(), nullable=True), 50 | sa.Column('teamId', sa.String(), nullable=True), 51 | sa.Column('created_at', sa.DateTime(), nullable=True), 52 | sa.Column('updated_at', sa.DateTime(), nullable=True), 53 | sa.ForeignKeyConstraint(['teamId'], ['teams.id'], ), 54 | sa.PrimaryKeyConstraint('id') 55 | ) 56 | op.create_table('soldiers', 57 | sa.Column('id', sa.String(), nullable=False), 58 | sa.Column('instruction', sa.String(), nullable=True), 59 | sa.Column('teamId', sa.String(), nullable=True), 60 | sa.Column('created_at', sa.DateTime(), nullable=True), 61 | sa.Column('updated_at', sa.DateTime(), nullable=True), 62 | sa.ForeignKeyConstraint(['teamId'], ['teams.id'], ), 63 | sa.PrimaryKeyConstraint('id') 64 | ) 65 | op.create_table('alarms', 66 | sa.Column('id', sa.String(), nullable=False), 67 | sa.Column('content', sa.String(), nullable=True), 68 | sa.Column('instruction', sa.String(), nullable=True), 69 | sa.Column('status', sa.Boolean(), nullable=True), 70 | sa.Column('created_at', sa.DateTime(), nullable=True), 71 | sa.Column('updated_at', sa.DateTime(), nullable=True), 72 | sa.Column('sentinelId', sa.String(), nullable=True), 73 | sa.ForeignKeyConstraint(['sentinelId'], ['sentinels.id'], ), 74 | sa.PrimaryKeyConstraint('id') 75 | ) 76 | op.create_table('observations', 77 | sa.Column('id', sa.String(), nullable=False), 78 | sa.Column('instruction', sa.String(), nullable=True), 79 | sa.Column('content', sa.String(), nullable=True), 80 | sa.Column('created_at', sa.DateTime(), nullable=True), 81 | sa.Column('updated_at', sa.DateTime(), nullable=True), 82 | sa.Column('scoutId', sa.String(), nullable=True), 83 | sa.ForeignKeyConstraint(['scoutId'], ['scouts.id'], ), 84 | sa.PrimaryKeyConstraint('id') 85 | ) 86 | op.create_table('reports', 87 | sa.Column('id', sa.String(), nullable=False), 88 | sa.Column('content', sa.String(), nullable=True), 89 | sa.Column('status', sa.Boolean(), nullable=True), 90 | sa.Column('created_at', sa.DateTime(), nullable=True), 91 | sa.Column('updated_at', sa.DateTime(), nullable=True), 92 | sa.Column('soldierId', sa.String(), nullable=True), 93 | sa.ForeignKeyConstraint(['soldierId'], ['soldiers.id'], ), 94 | sa.PrimaryKeyConstraint('id') 95 | ) 96 | # ### end Alembic commands ### 97 | 98 | 99 | def downgrade() -> None: 100 | # ### commands auto generated by Alembic - please adjust! ### 101 | op.drop_table('reports') 102 | op.drop_table('observations') 103 | op.drop_table('alarms') 104 | op.drop_table('soldiers') 105 | op.drop_table('sentinels') 106 | op.drop_table('scouts') 107 | op.drop_table('teams') 108 | op.drop_table('users') 109 | # ### end Alembic commands ### 110 | -------------------------------------------------------------------------------- /assets/bot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edmar/whenx/6cc32f811af93c33dbad7f5ab249ec01253e56a9/assets/bot.png -------------------------------------------------------------------------------- /assets/bot2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edmar/whenx/6cc32f811af93c33dbad7f5ab249ec01253e56a9/assets/bot2.png -------------------------------------------------------------------------------- /assets/bot3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edmar/whenx/6cc32f811af93c33dbad7f5ab249ec01253e56a9/assets/bot3.png -------------------------------------------------------------------------------- /assets/diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edmar/whenx/6cc32f811af93c33dbad7f5ab249ec01253e56a9/assets/diagram.png -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "whenx" 3 | version = "0.1.0" 4 | description = "" 5 | authors = ["Edmar Ferreira "] 6 | readme = "README.md" 7 | 8 | [tool.poetry.dependencies] 9 | python = "^3.11" 10 | sqlalchemy = "^2.0.15" 11 | mysql-connector-python = "^8.0.33" 12 | python-dotenv = "^1.0.0" 13 | openai = "^0.27.7" 14 | google-search-results = "^2.4.2" 15 | faiss-cpu = "^1.7.4" 16 | tiktoken = "^0.4.0" 17 | bs4 = "^0.0.1" 18 | langchain = "^0.0.200" 19 | playwright = "^1.35.0" 20 | alembic = "^1.11.1" 21 | schedule = "^1.2.0" 22 | resend = "^0.5.1" 23 | prettytable = "^3.8.0" 24 | 25 | 26 | [tool.poetry.group.dev.dependencies] 27 | ipython = "^8.13.2" 28 | pytest = "^7.3.1" 29 | ipykernel = "^6.23.1" 30 | 31 | [build-system] 32 | requires = ["poetry-core"] 33 | build-backend = "poetry.core.masonry.api" 34 | -------------------------------------------------------------------------------- /scripts/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edmar/whenx/6cc32f811af93c33dbad7f5ab249ec01253e56a9/scripts/__init__.py -------------------------------------------------------------------------------- /scripts/create_all.py: -------------------------------------------------------------------------------- 1 | from whenx.database import engine, Base 2 | from whenx.models import * 3 | Base.metadata.create_all(engine) 4 | 5 | -------------------------------------------------------------------------------- /scripts/delete_all_data.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from whenx.database import db 4 | from sqlalchemy import MetaData 5 | 6 | 7 | def delete_all_data(): 8 | meta = MetaData() 9 | meta.reflect(bind=db.get_bind()) # Get the engine associated with the session 10 | 11 | # First, delete child table data 12 | child_tables = [table for table in meta.sorted_tables if table.foreign_keys] 13 | 14 | for table in child_tables: 15 | db.execute(table.delete()) 16 | 17 | # Then, delete parent table data 18 | parent_tables = [table for table in meta.sorted_tables if not table.foreign_keys] 19 | 20 | for table in parent_tables: 21 | db.execute(table.delete()) 22 | 23 | db.commit() 24 | 25 | 26 | if __name__ == "__main__": 27 | delete_all_data() 28 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edmar/whenx/6cc32f811af93c33dbad7f5ab249ec01253e56a9/tests/__init__.py -------------------------------------------------------------------------------- /tests/test_captain.py: -------------------------------------------------------------------------------- 1 | from whenx.models.captain import Captain 2 | from pytest import fixture 3 | 4 | 5 | @fixture 6 | def captain(): 7 | mission = "when Haruki Murakami releases a new book, write a report about it." 8 | captain = Captain(mission) 9 | yield captain 10 | 11 | 12 | def test_run(captain): 13 | team = captain.run() 14 | assert team.scouts[0].instruction == "What is the new Haruki Murakami book? return the answer." 15 | assert team.sentinels[0].instruction == "Was a new Haruki Murakami book released? Reply with (Yes/No) and the name of the book." 16 | assert team.soldier[0].instruction == "Write a report about it." 17 | 18 | 19 | def test_run_team(captain): 20 | team = captain.run() 21 | report = team.run() 22 | assert report == "A new Haruki Murakami book was released." 23 | -------------------------------------------------------------------------------- /tests/test_compare_observations.py: -------------------------------------------------------------------------------- 1 | from pytest import fixture 2 | from whenx.services.compare_observations import compare_observations 3 | from whenx.models.scout import Observation 4 | 5 | 6 | @fixture(scope="function") 7 | def no_change(): 8 | observation_1 = Observation(instruction="What was the last book by Murakami?", content="The last book is Killing Comendatore", created_at="2019-01-01 00:00:00") 9 | observation_2 = Observation(instruction="What was the last book by Murakami?", content="Killing Comendatore", created_at="2019-01-02 00:00:00") 10 | obeservations = [observation_1, observation_2] 11 | yield obeservations 12 | 13 | 14 | @fixture(scope="function") 15 | def change(): 16 | observation_1 = Observation(instruction="What was the last book by Murakami?", content="The books is Killing Comendatore", created_at="2019-01-01 00:00:00") 17 | observation_2 = Observation(instruction="What was the last book by Murakami?", content="The latest book by Haruki Murakami is The Forbidden Worlds of Haruki Murakami.", created_at="2019-01-02 00:00:00") 18 | obeservations = [observation_1, observation_2] 19 | yield obeservations 20 | 21 | 22 | def test_compare_observations_no_change(no_change): 23 | instruction = "Was a new book released? Reply with (Yes/No) and the title." 24 | result = compare_observations(instruction, no_change) 25 | # check if the result.content contains "No" 26 | assert "No" in result.content 27 | 28 | 29 | def test_compare_observations_change(change): 30 | instruction = "Was a new book released? Reply with (Yes/No) and the title." 31 | result = compare_observations(instruction, change) 32 | assert "Yes" in result.content 33 | -------------------------------------------------------------------------------- /tests/test_create_autogpt.py: -------------------------------------------------------------------------------- 1 | from whenx.services.create_autogpt import create_autogpt 2 | 3 | 4 | def test_create_autogpt(): 5 | instruction = "what was the latest book from Haruki Murakami?" 6 | agent = create_autogpt() 7 | result = agent.run(instruction) 8 | assert result is not None 9 | -------------------------------------------------------------------------------- /tests/test_create_report.py: -------------------------------------------------------------------------------- 1 | from pytest import fixture 2 | from whenx.services.create_report import create_report 3 | 4 | 5 | @fixture(scope="function") 6 | def instruction(): 7 | alarm = "Yes, the latest book by Haruki Murakami is The Forbidden Worlds of Haruki Murakami." 8 | instruction = f"{alarm} Write a report about it." 9 | yield instruction 10 | 11 | 12 | def test_create_report(instruction): 13 | report = create_report(instruction) 14 | assert report.status is True 15 | -------------------------------------------------------------------------------- /tests/test_scout.py: -------------------------------------------------------------------------------- 1 | from pytest import fixture 2 | from whenx.models import User, Scout, Team 3 | from whenx.database import db 4 | 5 | 6 | @fixture(scope="function") 7 | def scout(): 8 | # Setup 9 | user = User(name="test user", email="ed@gmail.com") 10 | db.add(user) 11 | db.commit() 12 | 13 | team = Team(name="test team", userId=user.id) 14 | db.add(team) 15 | db.commit() 16 | 17 | instruction = "what was the latest book by Haruki Murakami? return the answer." 18 | scout = Scout(instruction=instruction, teamId=team.id) 19 | db.add(scout) 20 | db.commit() 21 | 22 | yield scout 23 | 24 | # Teardown 25 | db.delete(scout) 26 | db.delete(team) 27 | db.delete(user) 28 | db.commit() 29 | 30 | 31 | def test_run(scout): 32 | observation = scout.run() 33 | assert observation.content is not None 34 | assert observation.scoutId == scout.id 35 | -------------------------------------------------------------------------------- /tests/test_send_email.py: -------------------------------------------------------------------------------- 1 | from whenx.services.send_email import send_email 2 | 3 | 4 | def test_send_email(): 5 | r = send_email( 6 | "alerts@whenx.ai", 7 | "edmaroferreira@gmail.com", 8 | "This this time testing my own email as sender.", 9 | "A new email alert.", 10 | ) 11 | assert r is not None 12 | -------------------------------------------------------------------------------- /tests/test_sentinel.py: -------------------------------------------------------------------------------- 1 | from pytest import fixture 2 | from whenx.models.sentinel import Sentinel 3 | from whenx.models.scout import Observation 4 | 5 | 6 | @fixture(scope="function") 7 | def sentinel(): 8 | # Setup 9 | sentinel = Sentinel(instruction="Was a new book released? Reply with (Yes/No) and the title.") 10 | yield sentinel 11 | 12 | # Teardown 13 | pass 14 | 15 | 16 | @fixture(scope="function") 17 | def no_change(): 18 | observation_1 = Observation(instruction="What was the last book by Murakami?", content="The last book is Killing Comendatore", created_at="2019-01-01 00:00:00") 19 | observation_2 = Observation(instruction="What was the last book by Murakami?", content="Killing Comendatore", created_at="2019-01-02 00:00:00") 20 | obeservations = [observation_1, observation_2] 21 | yield obeservations 22 | 23 | 24 | @fixture(scope="function") 25 | def change(): 26 | observation_1 = Observation(instruction="What was the last book by Murakami?", content="The books is Killing Comendatore", created_at="2019-01-01 00:00:00") 27 | observation_2 = Observation(instruction="What was the last book by Murakami?", content="The latest book by Haruki Murakami is The Forbidden Worlds of Haruki Murakami.", created_at="2019-01-02 00:00:00") 28 | obeservations = [observation_1, observation_2] 29 | yield obeservations 30 | 31 | 32 | def test_run_no_change(sentinel, no_change): 33 | alarm = sentinel.run(no_change) 34 | assert alarm.status is False 35 | 36 | 37 | def test_run_change(sentinel, change): 38 | alarm = sentinel.run(change) 39 | assert alarm.status is True 40 | -------------------------------------------------------------------------------- /tests/test_soldier.py: -------------------------------------------------------------------------------- 1 | from pytest import fixture 2 | from whenx.models.soldier import Soldier 3 | from whenx.models.sentinel import Alarm 4 | 5 | 6 | @fixture(scope="function") 7 | def soldier(): 8 | # Setup 9 | soldier = Soldier(instruction="Write a report about it.") 10 | yield soldier 11 | 12 | # Teardown 13 | pass 14 | 15 | 16 | @fixture(scope="function") 17 | def alarm_true(): 18 | alarm = Alarm(content="Yes, The latest book by Haruki Murakami is The Forbidden Worlds of Haruki Murakami.", status=True) 19 | yield alarm 20 | 21 | 22 | @fixture(scope="function") 23 | def alarm_false(): 24 | alarm = Alarm(content="No, The last book is Killing Comendatore", status=False) 25 | yield alarm 26 | 27 | 28 | def test_run_true(alarm_true, soldier): 29 | report = soldier.run(alarm_true) 30 | assert report.content is not None 31 | 32 | 33 | def test_run_false(alarm_false, soldier): 34 | report = soldier.run(alarm_false) 35 | assert report is False 36 | -------------------------------------------------------------------------------- /tests/test_team.py: -------------------------------------------------------------------------------- 1 | from pytest import fixture 2 | from whenx.models.team import Team 3 | from whenx.models.soldier import Soldier 4 | from whenx.models.scout import Scout 5 | from whenx.models.sentinel import Sentinel 6 | from whenx.database import db 7 | 8 | 9 | @fixture 10 | def team(): 11 | team = Team(name="test team") 12 | db.add(team) 13 | db.commit() 14 | scout = Scout(instruction="what was the latest book by Haruki murakami? return the answer.", teamId=team.id) 15 | sentinel = Sentinel(instruction="Was a new book released? Reply with (Yes/No) and the title.", teamId=team.id) 16 | soldier = Soldier(instruction="Write a report about it.", teamId=team.id) 17 | db.add(scout) 18 | db.add(sentinel) 19 | db.add(soldier) 20 | db.commit() 21 | 22 | yield team 23 | 24 | db.delete(soldier) 25 | db.delete(sentinel) 26 | db.delete(scout) 27 | db.delete(team) 28 | db.commit() 29 | 30 | 31 | def test_run(team): 32 | report = team.run() 33 | assert report.status is True 34 | -------------------------------------------------------------------------------- /whenx/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edmar/whenx/6cc32f811af93c33dbad7f5ab249ec01253e56a9/whenx/__init__.py -------------------------------------------------------------------------------- /whenx/__main__.py: -------------------------------------------------------------------------------- 1 | # Inside __main__.py in your whenx package 2 | import argparse 3 | from whenx.console import run, create, monitor, list, delete 4 | 5 | 6 | def main(): 7 | parser = argparse.ArgumentParser() 8 | parser.add_argument('command', help='The command to run.') 9 | parser.add_argument('--mission', help='The mission for the create command.') 10 | parser.add_argument('--id', help='The id for the delete command.') 11 | args = parser.parse_args() 12 | 13 | if args.command == 'run': 14 | run() # Call the run function 15 | elif args.command == 'create': 16 | if args.mission: 17 | create(args.mission) # Call the create function with the mission argument 18 | else: 19 | print('The create command requires a --mission argument.') 20 | elif args.command == 'monitor': 21 | monitor() 22 | elif args.command == 'list': 23 | list() 24 | elif args.command == 'delete': 25 | delete(args.id) 26 | else: 27 | print(f'Unknown command: {args.command}') 28 | 29 | 30 | if __name__ == '__main__': 31 | main() 32 | -------------------------------------------------------------------------------- /whenx/console.py: -------------------------------------------------------------------------------- 1 | from whenx.models import Team, Captain 2 | from whenx.database import db 3 | import schedule 4 | import time 5 | from prettytable import PrettyTable 6 | 7 | 8 | def create(mission): 9 | captain = Captain(mission) 10 | team = Team(name=mission) 11 | team = captain.run(team) 12 | print(f"Team created with mission: {team.name}") 13 | print(f"Scout: {team.scouts[0].instruction}") 14 | print(f"Sentinel: {team.sentinels[0].instruction}") 15 | print(f"Soldier: {team.soldiers[0].instruction}") 16 | 17 | 18 | def run(): 19 | # Get all teams from the database 20 | for team in db.query(Team).all(): 21 | # Run the team 22 | print(f"Team: {team.name}") 23 | print("Running...") 24 | report = team.run() 25 | if report: 26 | print(report.content) 27 | else: 28 | print("Nothing new to report\n") 29 | 30 | 31 | def monitor(): 32 | schedule.every().day.do(run) 33 | 34 | while True: 35 | schedule.run_pending() 36 | print('Waiting for next') 37 | time.sleep(60) 38 | 39 | 40 | def list(): 41 | teams = db.query(Team).all() 42 | 43 | # Create a new table with the column headers 44 | table = PrettyTable() 45 | table.field_names = ["ID", "Mission", "Last Update"] 46 | 47 | # Add each team to the table as a new row 48 | for team in teams: 49 | table.add_row([team.id, team.name, team.updated_at]) 50 | 51 | # Print the table to the console 52 | print(table) 53 | 54 | 55 | def delete(id): 56 | db.query(Team).filter(Team.id == id).delete() 57 | db.commit() 58 | -------------------------------------------------------------------------------- /whenx/database.py: -------------------------------------------------------------------------------- 1 | import os 2 | from dotenv import load_dotenv 3 | from sqlalchemy import create_engine 4 | from sqlalchemy.orm import sessionmaker, declarative_base 5 | 6 | # load environment variables from .env file 7 | load_dotenv() 8 | 9 | # get the value of the DATABASE_URL environment variable 10 | database_url = os.getenv('DATABASE_URL') 11 | 12 | engine = create_engine(database_url) 13 | Session = sessionmaker(bind=engine) 14 | db = Session() 15 | 16 | Base = declarative_base() 17 | -------------------------------------------------------------------------------- /whenx/models/__init__.py: -------------------------------------------------------------------------------- 1 | from .team import Team 2 | from .scout import Scout 3 | from .sentinel import Sentinel 4 | from .soldier import Soldier 5 | from .captain import Captain 6 | from .user import User -------------------------------------------------------------------------------- /whenx/models/captain.py: -------------------------------------------------------------------------------- 1 | from langchain.chat_models import ChatOpenAI 2 | from langchain.schema import HumanMessage, SystemMessage 3 | from whenx.models.team import Team 4 | from whenx.models.scout import Scout 5 | from whenx.models.sentinel import Sentinel 6 | from whenx.models.soldier import Soldier 7 | import re 8 | from whenx.database import db 9 | 10 | 11 | class Captain: 12 | 13 | def __init__(self, mission: str): 14 | self.mission = mission 15 | 16 | def run(self, team): 17 | prompts = self.generate_prompts() 18 | team = self.create_team(prompts, team) 19 | return team 20 | 21 | def initialize_team(self, prompts, team): 22 | db.add(team) 23 | db.commit() 24 | scout = Scout(instruction=prompts["scout"], teamId=team.id) 25 | sentinel = Sentinel(instruction=prompts["sentinel"], teamId=team.id) 26 | soldier = Soldier(instruction=prompts["soldier"], teamId=team.id) 27 | db.add(scout) 28 | db.add(sentinel) 29 | db.add(soldier) 30 | db.commit() 31 | return team 32 | 33 | def generate_prompts(self): 34 | system = """You are the captain of a team of scouts, sentinels, and soldiers. 35 | You generate instructions for your team to follow based on a mission. 36 | Scouts are responsible for gathering information from the internet. 37 | Sentinels are responsible for monitoring the observations of scouts for changes. 38 | Soldiers are responsible for writing reports. 39 | Instruction examples: 40 | Mission: When apple relseases a new product. 41 | Scout: What is the new apple product? return the answer. 42 | Sentinel: Was a new product released? Reply with (Yes/No) and the name of the product. 43 | Soldier: Write a report about it. 44 | """ 45 | 46 | prompt = f""" 47 | Complete the instructions for the scouts, sentinels, and soldiers. One per line. 48 | Mission:{self.mission} 49 | """ 50 | model = ChatOpenAI(model="gpt-4", temperature=0) 51 | messages = [ 52 | SystemMessage( 53 | content=system 54 | ), 55 | HumanMessage(content=prompt), 56 | ] 57 | response = model(messages) 58 | response = self.parse_response(response.content) 59 | return response 60 | 61 | def parse_response(self, response): 62 | lines = re.split(r'\n+', response.strip()) 63 | # Extract the relevant information from the lines 64 | prompts = {} 65 | prompts["scout"] = lines[0].split(": ")[1] 66 | prompts["sentinel"] = lines[1].split(": ")[1] 67 | prompts["soldier"] = lines[2].split(": ")[1] 68 | return prompts 69 | -------------------------------------------------------------------------------- /whenx/models/scout.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy.orm import relationship 2 | from sqlalchemy import Column, String, DateTime, ForeignKey 3 | from whenx.database import Base 4 | import uuid 5 | from sqlalchemy.sql import func 6 | from whenx.database import db 7 | from whenx.services import create_react_agent 8 | 9 | 10 | class Scout(Base): 11 | __tablename__ = "scouts" 12 | 13 | id = Column(String, primary_key=True, default=str(uuid.uuid4())) 14 | instruction = Column(String) 15 | teamId = Column(String, ForeignKey("teams.id")) 16 | created_at = Column(DateTime, default=func.now()) 17 | updated_at = Column(DateTime, default=func.now(), onupdate=func.now()) 18 | 19 | team = relationship('Team', back_populates='scouts') 20 | 21 | observations = relationship('Observation', back_populates='scout', cascade='all, delete-orphan') 22 | 23 | def run(self): 24 | agent = create_react_agent() 25 | result = agent.run(self.instruction) 26 | observation = Observation(instruction=self.instruction, content=result, scoutId=self.id) 27 | db.add(observation) 28 | db.commit() 29 | return observation 30 | 31 | 32 | class Observation(Base): 33 | __tablename__ = "observations" 34 | 35 | id = Column(String, primary_key=True, default=lambda: str(uuid.uuid4())) 36 | instruction = Column(String) 37 | content = Column(String) 38 | created_at = Column(DateTime, default=func.now()) 39 | updated_at = Column(DateTime, default=func.now(), onupdate=func.now()) 40 | scoutId = Column(String, ForeignKey("scouts.id")) 41 | 42 | scout = relationship("Scout", back_populates="observations") 43 | -------------------------------------------------------------------------------- /whenx/models/sentinel.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy.orm import relationship 2 | from sqlalchemy import Column, String, DateTime, ForeignKey, Boolean 3 | from whenx.database import Base 4 | import uuid 5 | from sqlalchemy.sql import func 6 | from whenx.services.compare_observations import compare_observations 7 | 8 | 9 | class Sentinel(Base): 10 | __tablename__ = "sentinels" 11 | 12 | id = Column(String, primary_key=True, default=lambda: str(uuid.uuid4())) 13 | instruction = Column(String) 14 | teamId = Column(String, ForeignKey("teams.id")) 15 | created_at = Column(DateTime, default=func.now()) 16 | updated_at = Column(DateTime, default=func.now(), onupdate=func.now()) 17 | 18 | team = relationship('Team', back_populates='sentinels') 19 | 20 | def run(self, observations=[]): 21 | # IF there is only one observation then we don't need to compare 22 | if len(observations) == 1: 23 | alarm = Alarm(instruction=self.instruction, content=observations[0].content, status=True) 24 | self.alarms.append(alarm) 25 | return alarm 26 | 27 | comparison = compare_observations(observations) 28 | status = "yes" in comparison.content.lower() 29 | alarm = Alarm(instruction=self.instruction, content=comparison.content, status=status, sentinelId=self.id) 30 | self.alarms.append(alarm) 31 | return alarm 32 | 33 | 34 | class Alarm(Base): 35 | __tablename__ = "alarms" 36 | 37 | id = Column(String, primary_key=True, default=lambda: str(uuid.uuid4())) 38 | content = Column(String) 39 | instruction = Column(String) 40 | status = Column(Boolean, default=False) 41 | created_at = Column(DateTime, default=func.now()) 42 | updated_at = Column(DateTime, default=func.now(), onupdate=func.now()) 43 | sentinelId = Column(String, ForeignKey("sentinels.id")) 44 | 45 | sentinel = relationship("Sentinel", backref="alarms") 46 | -------------------------------------------------------------------------------- /whenx/models/soldier.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy.orm import relationship 2 | from sqlalchemy import Column, String, DateTime, ForeignKey, Boolean 3 | from whenx.database import Base 4 | import uuid 5 | from sqlalchemy.sql import func 6 | from whenx.services.create_report import create_report 7 | from whenx.services.send_email import send_email 8 | from dotenv import load_dotenv 9 | import os 10 | 11 | load_dotenv() 12 | 13 | 14 | class Soldier(Base): 15 | __tablename__ = "soldiers" 16 | 17 | id = Column(String, primary_key=True, default=lambda: str(uuid.uuid4())) 18 | instruction = Column(String) 19 | teamId = Column(String, ForeignKey("teams.id")) 20 | created_at = Column(DateTime, default=func.now()) 21 | updated_at = Column(DateTime, default=func.now(), onupdate=func.now()) 22 | 23 | team = relationship('Team', back_populates='soldiers') 24 | 25 | def run(self, alarm): 26 | if alarm.status is True: 27 | instruction = f"{alarm.content}{self.instruction}" 28 | report_content = create_report(instruction) 29 | report = Report(content=report_content, teamId=self.team.id, status=True) 30 | self.reports.append(report) 31 | self.send_report(self.team.name, report.content) 32 | return report 33 | else: 34 | return False 35 | 36 | def send_report(self, mission, report): 37 | sender = os.getenv("SENDER_EMAIL") 38 | email = os.getenv("USER_EMAIL") 39 | subject = f"Whenx Alert: {mission}" 40 | send_email(sender, email, subject, report) 41 | 42 | 43 | class Report(Base): 44 | __tablename__ = "reports" 45 | 46 | id = Column(String, primary_key=True, default=lambda: str(uuid.uuid4())) 47 | content = Column(String) 48 | status = Column(Boolean, default=False) 49 | created_at = Column(DateTime, default=func.now()) 50 | updated_at = Column(DateTime, default=func.now(), onupdate=func.now()) 51 | soldierId = Column(String, ForeignKey("soldiers.id")) 52 | teamId = Column(String, ForeignKey("teams.id")) 53 | 54 | soldier = relationship("Soldier", backref="reports") 55 | -------------------------------------------------------------------------------- /whenx/models/team.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import Column, String, DateTime, func, ForeignKey 2 | from sqlalchemy.orm import relationship 3 | from whenx.database import Base 4 | import uuid 5 | 6 | 7 | class Team(Base): 8 | __tablename__ = "teams" 9 | 10 | id = Column(String, primary_key=True, default=lambda: str(uuid.uuid4())) 11 | name = Column(String) 12 | description = Column(String) 13 | userId = Column(String, ForeignKey("users.id")) 14 | created_at = Column(DateTime, default=func.now()) 15 | updated_at = Column(DateTime, default=func.now(), onupdate=func.now()) 16 | 17 | scouts = relationship('Scout', back_populates='team') 18 | sentinels = relationship('Sentinel', back_populates='team') 19 | soldiers = relationship('Soldier', back_populates='team') 20 | user = relationship("User", backref="teams") 21 | reports = relationship("Report", back_populates="team") 22 | 23 | def run(self): 24 | # Run the Scout 25 | scout = self.scouts[0] 26 | scout.run() 27 | # Get the last 2 observations 28 | observations = scout.observations[:2] 29 | # Run the Sentinel 30 | sentinel = self.sentinels[0] 31 | alarm = sentinel.run(observations) 32 | # Run the Soldier 33 | soldier = self.soldiers[0] 34 | report = soldier.run(alarm) 35 | # update the updated_at field 36 | self.updated_at = func.now() 37 | return report 38 | -------------------------------------------------------------------------------- /whenx/models/user.py: -------------------------------------------------------------------------------- 1 | import uuid 2 | from sqlalchemy import Column, String 3 | from whenx.database import Base 4 | 5 | 6 | class User(Base): 7 | __tablename__ = "users" 8 | 9 | id = Column(String, primary_key=True, default=lambda: str(uuid.uuid4())) 10 | name = Column(String(50)) 11 | email = Column(String(120), unique=True) 12 | 13 | def __repr__(self): 14 | return "" % (self.name, self.email) 15 | -------------------------------------------------------------------------------- /whenx/services/__init__.py: -------------------------------------------------------------------------------- 1 | from .create_autogpt import create_autogpt 2 | from .create_react_agent import create_react_agent 3 | from .create_report import create_report 4 | from .create_plan_agent import create_plan_agent 5 | from .compare_observations import compare_observations 6 | -------------------------------------------------------------------------------- /whenx/services/compare_observations.py: -------------------------------------------------------------------------------- 1 | from langchain.chat_models import ChatOpenAI 2 | from langchain.schema import HumanMessage, SystemMessage 3 | 4 | 5 | def compare_observations(observations: list): 6 | llm = ChatOpenAI(model="gpt-4", temperature=0) 7 | prompt = f"""Compare the two answers and decide if a new report is necessary. Is a new report necessary? (Yes/No) 8 | Question, Answer, Date 9 | {observations[0].instruction},{observations[0].content}, {observations[0].created_at} 10 | {observations[1].instruction},{observations[1].content}, {observations[1].created_at}""" 11 | messages = [ 12 | SystemMessage( 13 | content="You are a helpful assistant that helps people compare observations." 14 | ), 15 | HumanMessage(content=prompt), 16 | ] 17 | result = llm(messages) 18 | return result 19 | -------------------------------------------------------------------------------- /whenx/services/create_autogpt.py: -------------------------------------------------------------------------------- 1 | from langchain.utilities import SerpAPIWrapper 2 | from langchain.agents import Tool 3 | from langchain.vectorstores import FAISS 4 | from langchain.docstore import InMemoryDocstore 5 | from langchain.embeddings import OpenAIEmbeddings 6 | from langchain.experimental import AutoGPT 7 | from langchain.chat_models import ChatOpenAI 8 | 9 | 10 | class Response: 11 | def __init__(self, text): 12 | self.text = text 13 | 14 | def set_text(self, text): 15 | self.text = text 16 | 17 | 18 | def setup_tools(response): 19 | search = SerpAPIWrapper() 20 | 21 | tools = [ 22 | Tool( 23 | name="search", 24 | func=search.run, 25 | description="useful for when you need to answer questions about current events. You should ask targeted questions", 26 | ), 27 | Tool( 28 | name="return", 29 | func=response.set_text, 30 | description="useful to return the answer to the question you asked and finish the task", 31 | ), 32 | ] 33 | return tools 34 | 35 | 36 | def setup_memory(): 37 | # Define your embedding model 38 | embeddings_model = OpenAIEmbeddings() 39 | # Initialize the vectorstore as empty 40 | import faiss 41 | 42 | embedding_size = 1536 43 | index = faiss.IndexFlatL2(embedding_size) 44 | vectorstore = FAISS(embeddings_model.embed_query, index, InMemoryDocstore({}), {}) 45 | return vectorstore 46 | 47 | 48 | def setup_autogpt(response): 49 | tools = setup_tools(response) 50 | vectorstore = setup_memory() 51 | agent = AutoGPT.from_llm_and_tools( 52 | ai_name="Tom", 53 | ai_role="Assistant", 54 | tools=tools, 55 | llm=ChatOpenAI(temperature=0), 56 | memory=vectorstore.as_retriever(), 57 | ) 58 | # Set verbose to be true 59 | agent.chain.verbose = True 60 | return agent 61 | 62 | 63 | class Agent(): 64 | 65 | def __init__(self): 66 | self.response = Response("") 67 | self.autogpt = setup_autogpt(self.response) 68 | 69 | def run(self, instruction): 70 | self.autogpt.run([instruction]) 71 | return self.response.text 72 | 73 | 74 | def create_autogpt(): 75 | return Agent() 76 | -------------------------------------------------------------------------------- /whenx/services/create_plan_agent.py: -------------------------------------------------------------------------------- 1 | from langchain.chat_models import ChatOpenAI 2 | from langchain.experimental.plan_and_execute import PlanAndExecute, load_agent_executor, load_chat_planner 3 | from langchain import SerpAPIWrapper 4 | from langchain.agents.tools import Tool 5 | 6 | 7 | def create_plan_agent(): 8 | search = SerpAPIWrapper() 9 | tools = [ 10 | Tool( 11 | name="Search", 12 | func=search.results, 13 | description="useful for when you need to answer questions about current events" 14 | ) 15 | ] 16 | model = ChatOpenAI(temperature=0) 17 | planner = load_chat_planner(model) 18 | executor = load_agent_executor(model, tools, verbose=True) 19 | agent = PlanAndExecute(planner=planner, executor=executor, verbose=True) 20 | return agent 21 | -------------------------------------------------------------------------------- /whenx/services/create_react_agent.py: -------------------------------------------------------------------------------- 1 | from langchain.agents import load_tools 2 | from langchain.agents import initialize_agent 3 | from langchain.agents import AgentType 4 | from langchain.chat_models import ChatOpenAI 5 | 6 | from dotenv import load_dotenv 7 | load_dotenv() 8 | 9 | 10 | def create_react_agent(agent_type=AgentType.ZERO_SHOT_REACT_DESCRIPTION): 11 | llm = ChatOpenAI(model='gpt-4', temperature=0) 12 | tools = load_tools(["serpapi"], llm=llm) 13 | agent = initialize_agent(tools, llm, agent=agent_type) 14 | return agent 15 | -------------------------------------------------------------------------------- /whenx/services/create_report.py: -------------------------------------------------------------------------------- 1 | from whenx.services.create_react_agent import create_react_agent 2 | 3 | 4 | def create_report(instruction): 5 | agent = create_react_agent() 6 | report = agent.run(instruction) 7 | return report 8 | -------------------------------------------------------------------------------- /whenx/services/create_team.py: -------------------------------------------------------------------------------- 1 | from whenx.models import Scout, Sentinel, Soldier 2 | from whenx.database import db 3 | 4 | 5 | def create_team(team): 6 | scout = Scout(instruction=team.name, teamId=team.id) 7 | sentinel = Sentinel(instruction=team.name, teamId=team.id) 8 | soldier = Soldier(instruction="Write a report about the answer.", teamId=team.id) 9 | db.add(scout) 10 | db.add(sentinel) 11 | db.add(soldier) 12 | db.commit() 13 | team.initialized = True 14 | db.add(team) 15 | db.commit() 16 | return team 17 | -------------------------------------------------------------------------------- /whenx/services/send_email.py: -------------------------------------------------------------------------------- 1 | import resend 2 | import os 3 | from dotenv import load_dotenv 4 | 5 | load_dotenv() 6 | 7 | resend.api_key = os.getenv("RESEND_API_KEY") 8 | 9 | 10 | def send_email(sender, to, subject, html): 11 | r = resend.Emails.send({"from": sender, "to": to, "subject": subject, "html": html}) 12 | return r 13 | --------------------------------------------------------------------------------