├── .env.example ├── .gitignore ├── .vscode └── launch.json ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── docs ├── .gitignore ├── README.md ├── babel.config.js ├── docs │ ├── genworlds-framework │ │ ├── _category_.json │ │ ├── actions.md │ │ ├── agents │ │ │ ├── _category_.json │ │ │ ├── agents.md │ │ │ └── thought_actions.md │ │ ├── objects.md │ │ └── worlds.md │ ├── get-started │ │ ├── _category_.json │ │ ├── intro.md │ │ └── quickstart.md │ └── tutorials │ │ ├── _category_.json │ │ ├── first_custom_agent.md │ │ ├── foundational_rag.md │ │ └── simple_collaboration_method.md ├── docusaurus.config.js ├── package-lock.json ├── package.json ├── sidebars.js ├── src │ ├── components │ │ ├── HomepageFeatures │ │ │ ├── index.js │ │ │ └── styles.module.css │ │ └── HomepageQuotes │ │ │ ├── index.js │ │ │ └── styles.module.css │ ├── css │ │ └── custom.css │ └── pages │ │ ├── index.js │ │ ├── index.module.css │ │ └── markdown-page.md ├── static │ ├── .nojekyll │ └── img │ │ ├── GenWorlds-Platform.png │ │ ├── android-chrome-192x192.png │ │ ├── android-chrome-512x512.png │ │ ├── apple-touch-icon.png │ │ ├── dna.svg │ │ ├── docusaurus.png │ │ ├── emojis │ │ ├── 1F3D7.svg │ │ ├── E202.svg │ │ ├── E249.svg │ │ ├── openmoji_alien_monster.svg │ │ ├── openmoji_brain.svg │ │ ├── openmoji_bricks.svg │ │ ├── openmoji_bullseye.svg │ │ └── openmoji_heart.svg │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ ├── favicon.ico │ │ ├── genworlds-social-card.png │ │ ├── genworlds_sample.png │ │ ├── logo.png │ │ ├── logo.svg │ │ ├── logo_with_text_white.webp │ │ └── site.webmanifest └── yarn.lock ├── genworlds ├── __init__.py ├── agents │ ├── __init__.py │ ├── abstracts │ │ ├── __init__.py │ │ ├── action_planner.py │ │ ├── agent.py │ │ ├── agent_state.py │ │ ├── state_manager.py │ │ ├── thought.py │ │ └── thought_action.py │ ├── concrete │ │ ├── __init__.py │ │ └── basic_assistant │ │ │ ├── __init__.py │ │ │ ├── action_planner.py │ │ │ ├── actions.py │ │ │ ├── agent.py │ │ │ ├── state_manager.py │ │ │ ├── thoughts │ │ │ ├── __init__.py │ │ │ ├── action_schema_selector.py │ │ │ └── event_filler.py │ │ │ └── utils.py │ ├── memories │ │ ├── __init__.py │ │ └── simulation_memory.py │ └── utils │ │ ├── __init__.py │ │ └── validate_action.py ├── events │ ├── __init__.py │ └── abstracts │ │ ├── __init__.py │ │ ├── action.py │ │ └── event.py ├── objects │ ├── __init__.py │ └── abstracts │ │ ├── __init__.py │ │ └── object.py ├── simulation │ ├── __init__.py │ ├── simulation.py │ ├── sockets │ │ ├── __init__.py │ │ ├── client.py │ │ ├── handlers │ │ │ ├── __init__.py │ │ │ └── event_handler.py │ │ ├── server.py │ │ └── test_client.py │ └── utils │ │ ├── __init__.py │ │ └── launch_simulation.py ├── utils │ ├── __init__.py │ ├── logging_factory.py │ ├── schema_to_model.py │ └── test_user.py └── worlds │ ├── __init__.py │ ├── abstracts │ ├── world.py │ └── world_entity.py │ └── concrete │ ├── __init__.py │ ├── base │ ├── __init__.py │ ├── actions.py │ └── world.py │ ├── community_chat_interface │ ├── __init__.py │ ├── actions.py │ └── world.py │ └── location_based │ ├── __init__.py │ ├── actions.py │ └── world.py ├── poetry.lock ├── pyproject.toml ├── requirements.txt └── use_cases ├── custom_qna_agent └── first_custom_agent.ipynb ├── foundational_rag ├── foundational_rag_tutorial.ipynb └── objects │ ├── __init__.py │ ├── local_storage_bucket.py │ └── qdrant_bucket.py ├── roundtable ├── __init__.py ├── agents │ └── roundtable_agent.py ├── events.py ├── migrations │ └── chroma_to_qdrant_migration.py ├── mocked_socket_generator.py ├── objects │ ├── __init__.py │ ├── blackboard.py │ ├── job.py │ └── microphone.py ├── roundtable_cli_config_example.json ├── thoughts │ ├── event_filler_thought.py │ ├── navigation_thought.py │ └── podcast_thought.py ├── token_bearer_tutorial.ipynb ├── world_definitions │ └── default_world_definition.yaml └── world_setup.py └── simple_collaboration_method ├── __init__.py └── simple-collaboration-method.ipynb /.env.example: -------------------------------------------------------------------------------- 1 | OPENAI_API_KEY= 2 | # DEBUG, INFO, WARNING, ERROR, CRITICAL 3 | LOGGING_LEVEL=INFO -------------------------------------------------------------------------------- /.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 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 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 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | 131 | # use-cases 132 | databases/ -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | 5 | { 6 | "name": "Start Socket Server", 7 | "type": "python", 8 | "request": "launch", 9 | "program": "genworlds/sockets/world_socket_server.py", 10 | "console": "externalTerminal", 11 | "env": { 12 | "PYTHONPATH": "${workspaceRoot}" 13 | }, 14 | }, 15 | { 16 | "name": "Start CLI", 17 | "type": "python", 18 | "request": "launch", 19 | "program": "genworlds/interfaces/cli/run.py", 20 | "console": "externalTerminal", 21 | "env": { 22 | "PYTHONPATH": "${workspaceRoot}" 23 | }, 24 | }, 25 | { 26 | "name": "Start Roundtable World", 27 | "type": "python", 28 | "request": "launch", 29 | "program": "use_cases/roundtable/world_setup.py", 30 | "console": "integratedTerminal", 31 | "env": { 32 | "PYTHONPATH": "${workspaceRoot}" 33 | }, 34 | }, 35 | { 36 | "name": "Start Mocked Socket Generator", 37 | "type": "python", 38 | "request": "launch", 39 | "program": "use_cases/roundtable/mocked_socket_generator.py", 40 | "console": "integratedTerminal", 41 | "env": { 42 | "PYTHONPATH": "${workspaceRoot}" 43 | }, 44 | }, 45 | ], 46 | "compounds": [ 47 | { 48 | "name": "Launch Full Stack", 49 | "configurations": [ 50 | "Start Socket Server", 51 | "Start CLI", 52 | "Start Roundtable World", 53 | ], 54 | "stopAll": true 55 | } 56 | ] 57 | } -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to GenWorlds 2 | 3 | Hi there! Thank you for even being interested in contributing to GenWorlds. 4 | As an open source project in a rapidly developing field, we are extremely open to contributions, whether they be in the form of new features, improved infra, better documentation, or bug fixes. 5 | 6 | ## 🚧 This page is under construction 7 | 8 | ## 🚩 GitHub Issues 9 | 10 | Our [issues](https://github.com/yeagerai/genworlds/issues) page is kept up to date 11 | with bugs, improvements, and feature requests. 12 | 13 | There is a taxonomy of labels to help with sorting and discovery of issues of interest. Please use these to help 14 | organize issues. 15 | 16 | If you start working on an issue, please assign it to yourself. 17 | 18 | If you are adding an issue, please try to keep it focused on a single, modular bug/improvement/feature. 19 | If two issues are related, or blocking, please link them rather than combining them. 20 | 21 | We will try to keep these issues as up to date as possible, though 22 | with the rapid rate of develop in this field some may get out of date. 23 | If you notice this happening, please let us know. 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 YeagerAI 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 | # 🧬🌍 GenWorlds - The Collaborative AI Agent Framework 2 | 3 | [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://opensource.org/license/mit/) [![Discord](https://dcbadge.vercel.app/api/server/VpfmXEMN66?compact=true&style=flat)](https://discord.gg/VpfmXEMN66) [![Twitter](https://img.shields.io/twitter/url/https/twitter.com/yeagerai.svg?style=social&label=Follow%20%40YeagerAI)](https://twitter.com/yeagerai) [![GitHub star chart](https://img.shields.io/github/stars/yeagerai/genworlds?style=social)](https://star-history.com/#yeagerai/genworlds) 4 | 5 | **Quick install:** `pip install genworlds` 6 | 7 | **Yeager Support:** We are standing by to provide extensive support as your GenWorlds project transitions to the production phase or If you are an enterprise looking to work directly with Yeager.ai to build custom GenAI applications. Please complete [this short form](https://share.hsforms.com/1EO76EZ_CTDGCiqRYtdpkJwc4zk8) and we will be get back to you shortly. 8 | 9 | ## ⚠️ Warnings 10 | 11 | - **GenWorlds is under active development** 12 | - **Using GenWorlds can be costly due to API calls** 13 | 14 | ## 👀 About 15 | 16 | GenWorlds is the event-based communication framework for building multi-agent systems. 17 | 18 | Using a websocket, GenWorlds provides a platform for creating interactive environments where AI agents asynchronously interact with each other and their environments to collectively execute complex tasks. 19 | 20 | Drawing inspiration from the seminal research paper ["Generative Agents: Interactive Simulacra of Human Behavior"](https://arxiv.org/abs/2304.03442) by Stanford and Google researchers, GenWorlds provides a platform for creating flexible, scalable, and interactive environments where AI agents can exist, communicate asynchronously, interact with diverse objects, and form new memories. 21 | 22 | These agents communicate with the world through a WebSocket server, promoting ease of UI construction, deployment, and future scalability. 23 | 24 | The current version of GenWorlds is powered by [OpenAI's GPT API](https://openai.com/product#made-for-developers), [Langchain](https://python.langchain.com/en/latest/index.html), [Qdrant](https://cloud.qdrant.io?ref=yeagerai), and was inspired by [AutoGPT](https://github.com/Significant-Gravitas/Auto-GPT). 25 | 26 | ## 📖 The Docs 27 | 28 | Detailed information of how to use the framework can be found at [GenWorlds Docs](https://genworlds.com/docs/get-started/intro). 29 | 30 | ## 🚀 Key Features 31 | 32 | - 🌐 **Customizable Interactive Environments:** Design unique GenWorld environments, tailored to your project's needs, filled with interactive objects and potential actions for your agents. 33 | 34 | - 🎯 **Goal-Oriented Generative Autonomous Agents:** Utilize AI agents powered by Langchain that are driven by specific objectives and can be easily extended and programmed to simulate complex behaviors and solve intricate problems. 35 | 36 | - 🧩 **Shared Objects:** Populate your world with shared objects, creating opportunities for your agents to interact with their environment and achieve their goals. 37 | 38 | - 💡 **Dynamic Memory Management:** Equip your agents with the ability to store, recall, and learn from past experiences, enhancing their decision-making and interaction capabilities. 39 | 40 | - ⚡ **Scalability:** Benefit from threading and WebSocket communication for real-time interaction between agents, ensuring the platform can easily scale up as your needs grow. 41 | 42 | ## 🛠️ Getting Started 43 | 44 | ### Follow the Quickstart Guide 45 | 46 | The best way to get started with GenWorlds is to follow the [Quickstart guide](https://genworlds.com/docs/get-started/quickstart). 47 | 48 | ### Run it with the Community Tooling 49 | 50 | The easiest way to start using genworlds with a graphical UI is through the [GenWorlds-Community](https://github.com/yeagerai/genworlds-community). Then you will be able to easily connect your `GenWorld` with a ChatGPT-like UI with different rooms for all the agents. 51 | 52 | ## Contributing 53 | 54 | As an open-source project in a rapidly developing field, we are extremely open to contributions, whether it be in the form of a new feature, improved infrastructure, or better documentation. Please read our [CONTRIBUTING](https://github.com/yeagerai/genworlds/blob/main/CONTRIBUTING.md) for guidelines on how to submit your contributions. 55 | 56 | As the framework is in alpha, expect large changes to the codebase. 57 | 58 | ## License 59 | 60 | 🧬🌍 GenWorlds is released under the MIT License. Please see the [LICENSE file](https://github.com/yeagerai/genworlds/blob/main/LICENSE) for more information. 61 | 62 | ## Disclaimer 63 | 64 | This software is provided 'as-is', without any guarantees or warranties. By using GenWorlds, you agree to assume all associated risks, including but not limited to data loss, system issues, or any unforeseen challenges. 65 | 66 | The developers and contributors of GenWorlds are not responsible for any damages, losses, or consequences that may arise from its use. You alone are responsible for any decisions and actions taken based on the information or results produced by GenWorlds. 67 | 68 | Be mindful that usage of AI models, like GPT-4, can be costly due to their token usage. By using GenWorlds, you acknowledge that you are responsible for managing your own token usage and related costs. 69 | 70 | As an autonomous system, GenWorlds may produce content or execute actions that may not align with real-world business practices or legal requirements. You are responsible for ensuring all actions or decisions align with all applicable laws, regulations, and ethical standards. 71 | 72 | By using GenWorlds, you agree to indemnify, defend, and hold harmless the developers, contributors, and any associated parties from any claims, damages, losses, liabilities, costs, and expenses (including attorney's fees) that might arise from your use of this software or violation of these terms. 73 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | /node_modules 3 | 4 | # Production 5 | /build 6 | 7 | # Generated files 8 | .docusaurus 9 | .cache-loader 10 | 11 | # Misc 12 | .DS_Store 13 | .env.local 14 | .env.development.local 15 | .env.test.local 16 | .env.production.local 17 | 18 | npm-debug.log* 19 | yarn-debug.log* 20 | yarn-error.log* 21 | 22 | # Created by https://www.toptal.com/developers/gitignore/api/yarn 23 | # Edit at https://www.toptal.com/developers/gitignore?templates=yarn 24 | 25 | ### yarn ### 26 | # https://yarnpkg.com/getting-started/qa#which-files-should-be-gitignored 27 | 28 | .yarn/* 29 | !.yarn/releases 30 | !.yarn/patches 31 | !.yarn/plugins 32 | !.yarn/sdks 33 | !.yarn/versions 34 | 35 | # if you are NOT using Zero-installs, then: 36 | # comment the following lines 37 | #!.yarn/cache 38 | 39 | # and uncomment the following lines 40 | .pnp.* 41 | 42 | # End of https://www.toptal.com/developers/gitignore/api/yarn 43 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Website 2 | 3 | This website is built using [Docusaurus 2](https://docusaurus.io/), a modern static website generator. 4 | 5 | ### Installation 6 | 7 | ``` 8 | $ yarn 9 | ``` 10 | 11 | ### Local Development 12 | 13 | ``` 14 | $ yarn start 15 | ``` 16 | 17 | This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server. 18 | 19 | ### Build 20 | 21 | ``` 22 | $ yarn build 23 | ``` 24 | 25 | This command generates static content into the `build` directory and can be served using any static contents hosting service. 26 | 27 | ### Deployment 28 | 29 | Using SSH: 30 | 31 | ``` 32 | $ USE_SSH=true yarn deploy 33 | ``` 34 | 35 | Not using SSH: 36 | 37 | ``` 38 | $ GIT_USER= yarn deploy 39 | ``` 40 | 41 | If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch. 42 | -------------------------------------------------------------------------------- /docs/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [require.resolve('@docusaurus/core/lib/babel/preset')], 3 | }; 4 | -------------------------------------------------------------------------------- /docs/docs/genworlds-framework/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "The GenWorlds Framework", 3 | "position": 2, 4 | "link": { 5 | "type": "generated-index", 6 | "description": "Building blocks of the Genworlds Framework" 7 | } 8 | } -------------------------------------------------------------------------------- /docs/docs/genworlds-framework/actions.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | --- 4 | 5 | # Actions and events 6 | 7 | The GenWorlds framework is built around the concept of asynchronous communication, and thus event based communication. We think that is the natural way for autonomous AI agents to communicate with other entities because you never know when an agent is going to finish a task, or how it would react to a specific event and change its plan. 8 | 9 | So to implement event based communication, we decided to use websockets as the main communication channel. This allows us to have a very simple and flexible communication protocol, and also allows us to easily integrate with other systems such as backends or webapps. 10 | 11 | Now talking more specifically about how GenWorlds implements this event-based communication is through the concepts of events and actions. 12 | 13 | ## Events 14 | 15 | Events are mainly pieces of information that are sent from one entity to the world socket server, and thus update the state of the world. You can see it as small `json` payloads that inform the world about something that happened. We use pydantic BaseModels to define the structure of the events, so mainly all events must follow our `AbstractEvent` interface. 16 | 17 | ```python 18 | class AbstractEvent(ABC, BaseModel): 19 | event_type: str 20 | description: str 21 | summary: Optional[str] 22 | created_at: datetime = Field(default_factory=datetime.now) 23 | sender_id: str 24 | target_id: Optional[str] 25 | ``` 26 | 27 | As you can see, all events must have a `event_type` and a `description`. The `event_type` is a string that identifies the type of event, and the `description` is a human readable description of the event. The `summary` is an optional field that can be used to give a short summary of the event. The `created_at` field is the timestamp of when the event was created. The `sender_id` is the id of the entity that sent the event, and the `target_id` is the id of the entity that the event is targeted to. This is useful for example when an agent wants to speak to another agent. 28 | 29 | Here is a possible implementation of this kind of event: 30 | 31 | ```python 32 | class AgentSpeaksWithAgentEvent(AbstractEvent): 33 | event_type = "agent_speaks_with_agent_event" 34 | description = "An agent speaks with another agent." 35 | message: str 36 | ``` 37 | 38 | Then at runtime we must define the `sender_id`, the `target_id` and the `message`. That's where actions come into play. 39 | 40 | ## Actions 41 | 42 | Actions are the main way to define how an entity reacts to an event. We have to remember that all entities are in fact event-listeners connected to a socket server. Actions are defined by a `trigger_event_class` which is the event that triggers the action, and a `__call__` method that is called when the action is triggered. The `__call__` method receives the event that triggered the action as a parameter. 43 | 44 | Most of the time, actions also send events to the world socket server, and thus update the state of the world, after executing the routine of the `__call__` method. Here is an example of an action that is used in the basic utility layer of GenWorlds to enable user-agent communication: 45 | 46 | ```python 47 | class AgentSpeaksWithUserTriggerEvent(AbstractEvent): 48 | event_type = "agent_speaks_with_user_trigger_event" 49 | description = "An agent speaks with the user." 50 | message: str 51 | 52 | 53 | class AgentSpeaksWithUserEvent(AbstractEvent): 54 | event_type = "agent_speaks_with_user_event" 55 | description = "An agent speaks with the user." 56 | message: str 57 | 58 | 59 | class AgentSpeaksWithUser(AbstractAction): 60 | trigger_event_class = AgentSpeaksWithUserTriggerEvent 61 | description = "An agent speaks with the user." 62 | 63 | def __init__(self, host_object: AbstractObject): 64 | super().__init__(host_object=host_object) 65 | 66 | def __call__(self, event: AgentSpeaksWithUserTriggerEvent): 67 | self.host_object.send_event( 68 | AgentSpeaksWithUserEvent( 69 | sender_id=self.host_object.id, 70 | target_id=event.target_id, 71 | message=event.message, 72 | ) 73 | ) 74 | ``` 75 | 76 | As you can see, the `AgentSpeaksWithUser` action is defined by a `trigger_event_class` which is the event that triggers the action. In this case the `AgentSpeaksWithUserTriggerEvent` is triggered by the agent when it wants to speak with the user. The action then sends the `AgentSpeaksWithUserEvent` to the user with the message. Remember that all this communication happens asynchronously through the socket server where every entity (world included) has its own event-listener. 77 | 78 | ## Action based development 79 | 80 | Actions are the keystone of the framework. So as a developer you have to start thinking about what actions you want to define for your entities. For example, if you want to define a `Microphone` object, you must think about what actions you want to define for it. For example, you can define an action that is triggered when an agent speaks into the microphone, and then the action sends an event to the world socket server with the message. Then you can define an action that is triggered when an agent passes the microphone to another agent, and then the action sends an event to the world socket server with the new agent that is holding the microphone. And so on. 81 | -------------------------------------------------------------------------------- /docs/docs/genworlds-framework/agents/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Agents", 3 | "position": 4, 4 | "link": { 5 | "type": "generated-index", 6 | "description": "GenWorlds Agents Explained" 7 | } 8 | } -------------------------------------------------------------------------------- /docs/docs/genworlds-framework/agents/agents.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | --- 4 | 5 | # Agents 6 | 7 | Agents are the entities that are both active and reactive within the world. They have a set of goals and try to accomplish them by planning a series of actions. Also they can trigger non-determinstic actions named `ThoughtActions`. 8 | 9 | More specifically, agents are entities that inherit from `AbstractAgent`, our interface class that gives them the boundaries and conditions that they must follow. 10 | 11 | In particular, GenWorlds agents always work using the following mental model: 12 | 13 | ```python 14 | def think_n_do(self): 15 | """Continuously plans and executes actions based on the agent's state.""" 16 | while True: 17 | try: 18 | sleep(1) 19 | if self.state_manager.state.is_asleep: 20 | continue 21 | else: 22 | state = self.state_manager.get_updated_state() 23 | action_schema, trigger_event = self.action_planner.plan_next_action( 24 | state 25 | ) 26 | 27 | if action_schema.startswith(self.id): 28 | selected_action = [ 29 | action 30 | for action in self.actions 31 | if action.action_schema[0] == action_schema 32 | ][0] 33 | selected_action(trigger_event) 34 | else: 35 | self.send_event(trigger_event) 36 | except Exception as e: 37 | print(f"Error in think_n_do: {e}") 38 | traceback.print_exc() 39 | ``` 40 | 41 | And agents run two parallel threads, one that is the event listener and the other one that executes the `think_n_do` function. 42 | 43 | Here is the breakdown of this specific mental model at each step of their interaction with the world: 44 | 45 | 1. **Reviewing the world state and surrounding entities:** The agent assesses the environment it's and gets the available entities and actions. 46 | 47 | 2. **Selects next action to perform:** Based on its memories of what happened during the simulation and the available actions that it can choose to do, it selects which is going to be the next action that will help him get closer to its goals. 48 | 49 | 3. **Fills the triggering event:** Using its stored memories, the agent recalls past experiences outputs from other pre-defined `ThoughtActions`, it fills the next event that it will send to the socket. 50 | 51 | 4. **Executes the action or sends the event:** If the triggering event that it filled is the trigger of an agent's action, then it executes the action. And if that's not the case, then it sends the event to the socket. So the other entity will receive it and will be able to react to it. 52 | 53 | This interactive process fosters the emergence of complex, autonomous behavior, making each agent an active participant in the GenWorld. 54 | 55 | ## Agent State 56 | 57 | The agent's state is where all the information about the agent is stored. It's a `pydantic.BaseModel` that has the following fields: 58 | 59 | * `id`: Unique identifier of the agent. 60 | * `description`: Description of the agent. 61 | * `name`: Name of the agent. 62 | * `host_world_prompt`: Prompt of the host world. 63 | * `memory_ignored_event_types`: Set of event types that will be ignored and not added to memory of the agent. 64 | * `wakeup_event_types`: Events that can wake up the agent. 65 | * `action_schema_chains`: List of action schema chains that inhibit the action selector. 66 | * `goals`: List of goals of the agent. 67 | * `plan`: List of actions that form the plan. 68 | * `last_retrieved_memory`: Last retrieved memory of the agent. 69 | * `other_thoughts_filled_parameters`: Parameters filled by other thoughts. 70 | * `available_action_schemas`: Available action schemas with their descriptions. 71 | * `available_entities`: List of available entities in the environment. 72 | * `is_asleep`: Indicates whether the agent is asleep. 73 | * `current_action_chain`: List of action schemas that are currently being executed. 74 | 75 | Those fields are accessed during the `think_n_do` process to make decisions about the next action to perform. 76 | 77 | ## BasicAssistant 78 | 79 | The `BasicAssistant` is the simplest useful agent that you can create. It's a subclass of `AbstractAgent` that has a set of pre-defined actions that are useful for most agentic environments. Is the central part of the framework's basic utility layer. 80 | 81 | Also it can be easily extended to create more complex agents that inherit from it. In our experience, custom agents that inherit from `BasicAssistant` can cover most of the use cases that we have encountered. 82 | 83 | You can find more information about how to use agents and create custom agents in the tutorials section of this documentation. 84 | -------------------------------------------------------------------------------- /docs/docs/genworlds-framework/agents/thought_actions.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 2 3 | --- 4 | 5 | # Thought Actions 6 | 7 | Thought actions are a special type of action that agents can trigger. They are non-deterministic actions that can be triggered by agents to fill event parameters with non deterministic information. 8 | 9 | Here is the abstract interface that all thought actions must implement: 10 | 11 | ```python 12 | class ThoughtAction(AbstractAction): 13 | """ 14 | Abstract interface class for a Thought Action. 15 | 16 | This includes the list of thoughts required to fill the parameters of the trigger event. 17 | """ 18 | 19 | # {parameter_name: [thought_class, run_dict]} 20 | required_thoughts: Dict[str, Tuple[AbstractThought, dict]] 21 | ``` 22 | 23 | By adding a thought action to an agent, you can fill event parameters in a non-deterministic way. So before the agent fills the event that will send to the socket, it will look for the thoughts that it has to execute to fill some of the event parameters. And will execute those thoughts before going to the `event_filler_thought`. 24 | 25 | So to understand better what thought actions can do, we have to understand what a thought is. 26 | 27 | ## Thoughts 28 | 29 | Are esentially LLM calls that can be triggered by agents to fill event parameters with non deterministic information. 30 | 31 | Here is an example, the thought that we tipically use in `RAG` simulated worlds, to answer the user's question based on the data extracted from the different data structures: 32 | 33 | ```python 34 | class JustifiedAnswer(BaseModel): 35 | """Answers the user's question based on data extracted from different data structures.""" 36 | 37 | answer: str = Field( 38 | ..., 39 | description="The final answer to the user.", 40 | ) 41 | rationale: str = Field( 42 | ..., 43 | description="The rationale of why this is the correct answer to user's question.", 44 | ) 45 | references: Optional[List[str]] = Field( 46 | ..., description="References to documents extracted from metadata." 47 | ) 48 | 49 | class AnswerFromSourcesThought(AbstractThought): 50 | def __init__( 51 | self, 52 | agent_state: AbstractAgentState # other thoughts unique init arg, so everything goes through state 53 | ): 54 | self.agent_state = agent_state 55 | self.llm = ChatOpenAI( 56 | model="gpt-4", openai_api_key=openai_api_key, temperature=0.1 57 | ) 58 | 59 | def run(self): 60 | prompt = ChatPromptTemplate.from_messages( 61 | [ 62 | ("system", "You are {agent_name}, {agent_description}."), 63 | ( 64 | "system", 65 | "You are embedded in a simulated world with those properties {agent_world_state}", 66 | ), 67 | ("system", "Those are your goals: \n{goals}"), 68 | ( 69 | "system", 70 | "And this is your current plan to achieve the goals: \n{plan}", 71 | ), 72 | ( 73 | "system", 74 | "Here is your memories of all the events that you remember from being in this simulation: \n{memory}", 75 | ), 76 | ( 77 | "system", 78 | "This is the last user's question: \n{user_question}", 79 | ), 80 | ( 81 | "system", 82 | "This is relevant information related to the user's question: \n{relevant_info}", 83 | ), 84 | ("human", "{footer}"), 85 | ] 86 | ) 87 | 88 | chain = create_structured_output_chain( 89 | output_schema=JustifiedAnswer.schema(), 90 | llm=self.llm, 91 | prompt=prompt, 92 | verbose=True, 93 | ) 94 | response = chain.run( 95 | agent_name=self.agent_state.name, 96 | agent_description=self.agent_state.description, 97 | agent_world_state=self.agent_state.host_world_prompt, 98 | goals=self.agent_state.goals, 99 | plan=self.agent_state.plan, 100 | memory=self.agent_state.last_retrieved_memory, 101 | user_question=self.agent_state.users_question, 102 | relevant_info=self.agent_state.retrieved_information_from_data_structures, 103 | footer="""Provide a justified answer to user's question based on the different data structures, the rationale, and references when possible. 104 | """, 105 | ) 106 | response = JustifiedAnswer.parse_obj(response) 107 | return response 108 | ``` 109 | 110 | In this thought, we use LangChain and OpenAI Function calling to fill the `answer` and `rationale` parameters of the `JustifiedAnswer` schema by calling `gpt-3.5-turbo`. 111 | 112 | ## When to use thought actions 113 | 114 | For simple interactions with objects and other agents, you don't need to create specific thought actions, as the decisions are being made by the `action_schema_selector_thought` and the `event_filler_thought`. 115 | 116 | But when you need to fill event parameters with non-deterministic information, you can create a thought action that will be triggered by the agent to fill those parameters. 117 | 118 | By enhancing the `BasicAssistant` capabilities with more actions and thought actions, you can create more complex agents that probably cover most of the use cases that you will encounter. 119 | 120 | ## Action Chains 121 | 122 | Action chains are chains of actions that are executed sequentially. They are used to avoid the agent to be constantly selecting actions when we already know how the procedure should be in advance. It's a way to make the agent more efficient and avoid potential errors of the `action_shema_selector_thought`. 123 | -------------------------------------------------------------------------------- /docs/docs/genworlds-framework/objects.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 3 3 | --- 4 | 5 | # Objects 6 | 7 | Objects are the simplest GenWorlds event listener. Every object defines a unique set actions (usually deterministic), and is reactive. So listens to world events and reacts to them with its actions. Thus, enabling agents to accomplish specific tasks and work together in a dynamic environment. 8 | 9 | All objects must inherit from the `AbstractObject` interface class: 10 | 11 | ```python 12 | class AbstractObject(SimulationSocketEventHandler): 13 | """ 14 | A Class representing a generic object in the simulation. 15 | """ 16 | 17 | def __init__( 18 | self, 19 | name: str, 20 | id: str, 21 | description: str, 22 | host_world_id: str = None, 23 | actions: List[Type[AbstractAction]] = [], 24 | ): 25 | self.host_world_id = host_world_id 26 | self.name = name 27 | self.description = description 28 | 29 | super().__init__(id=id, actions=actions) 30 | ``` 31 | 32 | In particular, Worlds and Agents are also objects. 33 | 34 | Objects in GenWorlds are versatile and can be subclassed to achieve varying purposes, such as 'token bearers' or 'tools'. For instance, a 'token bearer' like a microphone in a podcast studio, can be passed among agents, fostering a collaborative environment. The agent currently holding the token (or the microphone) gains the ability to perform unique actions, like speaking to the audience. This mechanism introduces the notion of shared resources and cooperative interactions, reinforcing that objects aren't confined to one agent, but can be easily exchanged and used throughout the collective. 35 | 36 | A 'tool', on the other hand, can execute specific functions such as calling external APIs, running complex calculations, or triggering unique events in the world. 37 | 38 | This way, by defining various objects and mapping them to specific events, you can design rich and complex interactions in the GenWorlds environment. 39 | 40 | For instance a simple microphone object can be defined as follows: 41 | 42 | ```python 43 | class Microphone(AbstractObject): 44 | def __init__(self, id:str, holder_agent_id: str): 45 | self.holder_agent_id = holder_agent_id 46 | actions = [SendMicrophoneToAgent(host_object=self), AgentSpeaksIntoMicrophone(host_object=self)] 47 | 48 | super().__init__(name="Microphone", 49 | id=id, 50 | description="""A podcast microphone that allows the holder of it to speak to the 51 | audience. The speaker can choose to make a statement, ask a question, respond 52 | to a question, or make a joke.""", 53 | actions=actions 54 | ) 55 | 56 | ``` 57 | 58 | But as we explained before in the actions section of this docs, entities are mainly defined by their actions. So let's take a look at the actions that this microphone object can perform: 59 | 60 | ```python 61 | class SendMicrophoneToAgentEvent(AbstractEvent): 62 | event_type = "send_microphone_to_agent_event" 63 | description = "An agent sends the microphone to another agent." 64 | 65 | class NewHolderOfMicrophoneEvent(AbstractEvent): 66 | event_type = "new_holder_of_microphone_event" 67 | description = "Event that states who is the new holder of the microphone." 68 | new_holder_id:str 69 | 70 | class SendMicrophoneToAgent(AbstractAction): 71 | trigger_event_class = SendMicrophoneToAgentEvent 72 | description = "An agent sends the microphone to another agent." 73 | 74 | def __init__(self, host_object: AbstractObject): 75 | super().__init__(host_object=host_object) 76 | 77 | def __call__(self, event: SendMicrophoneToAgentEvent): 78 | self.host_object.holder_agent_id = event.target_id 79 | event = NewHolderOfMicrophoneEvent( 80 | sender_id=self.host_object.id, 81 | target_id=self.host_object.holder_agent_id 82 | new_holder=self.host_object.holder_agent_id 83 | ) 84 | self.host_object.send_event(event) 85 | ``` 86 | 87 | This is the action that makes the microphone object reactive. It listens to the `SendMicrophoneToAgentEvent` event, and when it is triggered, it changes the `holder_agent_id` attribute of the microphone object to the `target_id` of the event. Then it sends a `NewHolderOfMicrophoneEvent` event to the world, stating who is the new holder of the microphone. 88 | 89 | But microphones are not only for passing around. They are also for speaking into them. So let's take a look at the `AgentSpeaksIntoMicrophone` action: 90 | 91 | ```python 92 | class AgentSpeaksIntoMicrophoneTriggerEvent(AbstractEvent): 93 | event_type = "agent_speaks_into_microphone_trigger_event" 94 | description = "An agent sends the microphone to another agent." 95 | message:str 96 | 97 | class AgentSpeaksIntoMicrophoneEvent(AbstractEvent): 98 | event_type = "agent_speaks_into_microphone_event" 99 | description = "Event that states who is the new holder of the microphone." 100 | message:str 101 | 102 | class NotAllowedToSpeakEvent(AbstractEvent): 103 | event_type = "not_allowed_to_speak_event" 104 | description = "The agent who is trying to speak into the microphone is not allowed." 105 | message:str 106 | 107 | class AgentSpeaksIntoMicrophone(AbstractAction): 108 | trigger_event_class = SendMicrophoneToAgentEvent 109 | description = "An agent sends the microphone to another agent." 110 | 111 | def __init__(self, host_object: AbstractObject): 112 | super().__init__(host_object=host_object) 113 | 114 | def __call__(self, event: AgentSpeaksIntoMicrophoneTriggerEvent): 115 | if self.host_object.holder_agent_id == event.sender_id: 116 | event = AgentSpeaksIntoMicrophoneEvent( 117 | sender_id=event.sender_id, 118 | target_id=self.host_object.id, 119 | message=event.message, 120 | ) 121 | self.host_object.send_event(event) 122 | else: 123 | event = NotAllowedToSpeakEvent( 124 | sender_id=self.host_object.id, 125 | target_id=event.sender_id, 126 | message=f"You are not allowed to speak into the microphone as you are not the current holder of the microphone, keep waiting for now." 127 | ) 128 | self.host_object.send_event(event) 129 | ``` 130 | 131 | This action listens to the `AgentSpeaksIntoMicrophoneTriggerEvent` event, and when it is triggered, it checks if the sender of the event is the current holder of the microphone. If it is, then it sends a `AgentSpeaksIntoMicrophoneEvent` event to the world, stating that the agent is speaking into the microphone. If it is not, then it sends a `NotAllowedToSpeakEvent` event to the world, stating that the agent is not allowed to speak into the microphone. Thus, converting the microphone in a token bearer object for coordinating communication between agents. 132 | -------------------------------------------------------------------------------- /docs/docs/get-started/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Get Started", 3 | "position": 1, 4 | "link": { 5 | "type": "generated-index", 6 | "description": "Embark on your journey with the GenWorlds framework, exploring its robust capabilities and features. Learn the fundamentals of leveraging GenWorlds to craft intricate, dynamic worlds with ease and precision. Begin your adventure in world generation and uncover the potential that awaits." 7 | } 8 | } -------------------------------------------------------------------------------- /docs/docs/get-started/intro.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | --- 4 | 5 | # Introduction 6 | 7 | GenWorlds is the event-based communication framework for building multi-agent systems. 8 | 9 | Using a websocket, GenWorlds provides a platform for creating interactive environments where AI agents asynchronously interact with each other and their environments to collectively execute complex tasks. 10 | 11 | ## Main value props 12 | 13 | 1. **Make What You Want: Customizable Systems** 14 | - **Abstraction Layer:** Interface with basic primitives, allowing you to build agents, objects, and worlds without predefined limits. 15 | - **Deterministic & Non-Deterministic Processes:** Manage the reliability and accuracy of your system by controlling process types. 16 | 17 | 2. **Build Fast: Pre-Built Elements** 18 | - **Utility Layer:** Comes with ready-made agents, objects, and worlds, enabling quick setup while still permitting customization to cater to most use cases. 19 | 20 | 3. **Deploy Easily: Designed for Smooth Launches** 21 | - **Web-Socket Server at Core:** Each GenWorld is a FastAPI web-socket server, thus it can be dockerized and deployed without hassle. 22 | - **Versatile Connectivity:** The web-socket nature also means you can straightforwardly connect to frontends, backends, or your servers, much like any other web-socket server, offering broad interoperability with most web systems. 23 | 24 | ## Get Started 25 | 26 | Here is how to install GenWorlds, and get started with your first multi-agent system. We highly recommend to follow the [Quickstart guide](/docs/get-started/quickstart.md), but if your prefer, you can jump directly to the tutorials. 27 | 28 | ## Core Framework Primitives 29 | 30 | Before you dive into the specifics, it's crucial to understand the primitives that underpin GenWorlds: 31 | 32 | - [**Worlds:**](/docs/genworlds-framework/worlds.md) is the stage of action, tracking agents, objects, and world-specific attributes. It provides agents with real-time updates on the world state, available entities, actions, and events, facilitating interactions. 33 | 34 | - [**Objects:**](/docs/genworlds-framework/objects.md) are the essential interactive elements, each defined by unique action sets. We tend to use objects when we want to trigger deterministic processes. 35 | 36 | - [**Agents:**](/docs/genworlds-framework/agents/agents.md) Autonomous goal-driven entities, strategizing actions to interact with the world. They learn dynamically about the environment, utilizing objects around them to meet their objectives. 37 | 38 | - [**Actions:**](/docs/genworlds-framework/actions.md) Routines that are triggered by an event, and usually the end up sending another event to the socket. Is the main way to define worlds, objects, and agents. 39 | 40 | - [**Events:**](/docs/genworlds-framework/actions.md) Payloads of information that essentially are the state of the world. 41 | 42 | - [**Thoughts:**](/docs/genworlds-framework/agents/thought_actions.md) Essentially calls to LLMs that will non deterministically fill parameters of the events that will be sent to the socket. 43 | 44 | ## Tutorials 45 | 46 | The best way to understand the GenWorlds framework is to see it in action. The following tutorials will give you a taste of what you can achieve with GenWorlds. 47 | 48 | - [Simple Collaboration Method](/docs/tutorials/simple_collaboration_method.md) An example of a basic world, with two agents that cooperate to achieve a very simple task. 49 | 50 | - [Foundational in-house RAG World](/docs/tutorials/foundational_rag.md) An example of a RAG (Retrieval Augmented Generation) World that can be used as a foundational piece from where to expand and create a knowledge source of your projects. Here you will learn a lot about deterministic actions and objects. 51 | 52 | - [Custom Q&A Agent](/docs/tutorials/first_custom_agent.md) In this tutorial, you will learn how you can create custom autonomous agents that have multiple thoughts and how they get integrated into already existing worlds. 53 | 54 | 55 | 56 | Again, if you are new to the topic of autonomous AI Agents, we highly recommend to follow the [Quickstart guide](/docs/get-started/quickstart.md). 57 | -------------------------------------------------------------------------------- /docs/docs/tutorials/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Tutorials", 3 | "position": 6, 4 | "link": { 5 | "type": "generated-index", 6 | "description": "Some Tutorials that showcase some features of the GenWorlds framework so you can start playing with it now" 7 | } 8 | } -------------------------------------------------------------------------------- /docs/docusaurus.config.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | // Note: type annotations allow type checking and IDEs autocompletion 3 | 4 | const lightCodeTheme = require('prism-react-renderer/themes/github'); 5 | const darkCodeTheme = require('prism-react-renderer/themes/dracula'); 6 | 7 | /** @type {import('@docusaurus/types').Config} */ 8 | const config = { 9 | title: '🧬🌍 GenWorlds', 10 | tagline: '🧬🌍 GenWorlds is the event-based communication framework for building multi-agent systems.', 11 | favicon: 'img/favicon.ico', 12 | 13 | // Set the production url of your site here 14 | url: 'https://genworlds.com', 15 | // Set the // pathname under which your site is served 16 | // For GitHub pages deployment, it is often '//' 17 | baseUrl: '/', 18 | 19 | // GitHub pages deployment config. 20 | // If you aren't using GitHub pages, you don't need these. 21 | organizationName: 'YeagerAI', // Usually your GitHub org/user name. 22 | projectName: 'GenWorlds', // Usually your repo name. 23 | 24 | onBrokenLinks: 'throw', 25 | onBrokenMarkdownLinks: 'warn', 26 | 27 | // Even if you don't use internalization, you can use this field to set useful 28 | // metadata like html lang. For example, if your site is Chinese, you may want 29 | // to replace "en" with "zh-Hans". 30 | i18n: { 31 | defaultLocale: 'en', 32 | locales: ['en'], 33 | }, 34 | markdown: { 35 | mermaid: true, 36 | }, 37 | themes: ['@docusaurus/theme-mermaid'], 38 | presets: [ 39 | [ 40 | 'classic', 41 | /** @type {import('@docusaurus/preset-classic').Options} */ 42 | ({ 43 | docs: { 44 | sidebarPath: require.resolve('./sidebars.js'), 45 | // Please change this to your repo. 46 | // Remove this to remove the "edit this page" links. 47 | editUrl: 48 | 'https://github.com/yeagerai/genworlds/tree/main/docs', 49 | }, 50 | blog: { 51 | showReadingTime: true, 52 | // Please change this to your repo. 53 | // Remove this to remove the "edit this page" links. 54 | editUrl: 55 | 'https://medium.com/yeagerai', 56 | }, 57 | theme: { 58 | customCss: require.resolve('./src/css/custom.css'), 59 | }, 60 | googleTagManager: { 61 | containerId: 'GTM-M5ZW8WB', 62 | }, 63 | sitemap: { 64 | changefreq: 'weekly', 65 | priority: 0.5, 66 | ignorePatterns: ['/tags/**'], 67 | filename: 'sitemap.xml', 68 | }, 69 | }), 70 | ], 71 | ], 72 | 73 | themeConfig: 74 | /** @type {import('@docusaurus/preset-classic').ThemeConfig} */ 75 | ({ 76 | // Replace with your project's social card 77 | image: 'img/genworlds-social-card.png', 78 | metadata: [ 79 | {name: 'twitter:card', content: 'summary_large_image'}, 80 | {name: 'og:image', content: 'img/genworlds-social-card.png'}, 81 | {name: 'twitter:image', content: 'img/genworlds-social-card.png'}, 82 | ], 83 | navbar: { 84 | title: '🧬🌍 GenWorlds', 85 | items: [ 86 | { 87 | type: 'docSidebar', 88 | sidebarId: 'tutorialSidebar', 89 | position: 'left', 90 | label: 'Docs', 91 | }, 92 | // {to: '/blog', label: 'Blog', position: 'left'}, 93 | { 94 | href: 'https://github.com/yeagerai/genworlds', 95 | label: 'GitHub', 96 | position: 'right', 97 | }, 98 | ], 99 | }, 100 | footer: { 101 | style: 'dark', 102 | links: [ 103 | { 104 | title: 'Docs', 105 | items: [ 106 | { 107 | label: 'Introduction to GenWorlds', 108 | to: '/docs/get-started/intro', 109 | }, 110 | ], 111 | }, 112 | { 113 | title: 'Community', 114 | items: [ 115 | { 116 | label: 'Stack Overflow', 117 | href: 'https://stackoverflow.com/questions/tagged/genworlds', 118 | }, 119 | { 120 | label: 'Discord', 121 | href: 'https://discord.gg/22eCYpb3w2', 122 | }, 123 | { 124 | label: 'Twitter', 125 | href: 'https://twitter.com/yeagerai', 126 | }, 127 | ], 128 | }, 129 | { 130 | title: 'More', 131 | items: [ 132 | // { 133 | // label: 'Blog', 134 | // to: '/blog', 135 | // }, 136 | { 137 | label: 'GitHub', 138 | href: 'https://github.com/yeagerai/genworlds', 139 | }, 140 | ], 141 | }, 142 | ], 143 | copyright: `Copyright ©${new Date().getFullYear()} YeagerAI LLC.`, 144 | }, 145 | prism: { 146 | theme: lightCodeTheme, 147 | darkTheme: darkCodeTheme, 148 | }, 149 | algolia: { 150 | // The application ID provided by Algolia 151 | appId: 'XZUXW9E7JX', 152 | 153 | // Public API key: it is safe to commit it 154 | apiKey: 'f189788e1581d61e22a23c308b60d569', 155 | 156 | indexName: 'prod_GenWorlds_Docs', 157 | 158 | // Optional: see doc section below 159 | contextualSearch: true, 160 | 161 | // Optional: Replace parts of the item URLs from Algolia. Useful when using the same search index for multiple deployments using a different baseUrl. You can use regexp or string in the `from` param. For example: localhost:3000 vs myCompany.com/docs 162 | replaceSearchResultPathname: { 163 | from: '/docs/', // or as RegExp: /\/docs\// 164 | to: '/', 165 | }, 166 | }, 167 | }), 168 | }; 169 | 170 | module.exports = config; 171 | -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "genworlds-docs", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "docusaurus": "docusaurus", 7 | "start": "docusaurus start", 8 | "build": "docusaurus build", 9 | "swizzle": "docusaurus swizzle", 10 | "deploy": "docusaurus deploy", 11 | "clear": "docusaurus clear", 12 | "serve": "docusaurus serve", 13 | "write-translations": "docusaurus write-translations", 14 | "write-heading-ids": "docusaurus write-heading-ids" 15 | }, 16 | "dependencies": { 17 | "@docusaurus/core": "2.4.1", 18 | "@docusaurus/plugin-google-tag-manager": "^2.4.1", 19 | "@docusaurus/preset-classic": "2.4.1", 20 | "@docusaurus/theme-mermaid": "^2.4.1", 21 | "@mdx-js/react": "^1.6.22", 22 | "classnames": "^2.3.2", 23 | "clsx": "^1.2.1", 24 | "prism-react-renderer": "^1.3.5", 25 | "react": "^17.0.2", 26 | "react-dom": "^17.0.2", 27 | "react-hubspot-form": "^1.3.7", 28 | "react-slick": "^0.29.0", 29 | "slick-carousel": "^1.8.1" 30 | }, 31 | "devDependencies": { 32 | "@docusaurus/module-type-aliases": "2.4.1" 33 | }, 34 | "browserslist": { 35 | "production": [ 36 | ">0.5%", 37 | "not dead", 38 | "not op_mini all" 39 | ], 40 | "development": [ 41 | "last 1 chrome version", 42 | "last 1 firefox version", 43 | "last 1 safari version" 44 | ] 45 | }, 46 | "engines": { 47 | "node": ">=16.14" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /docs/sidebars.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Creating a sidebar enables you to: 3 | - create an ordered group of docs 4 | - render a sidebar for each doc of that group 5 | - provide next/previous navigation 6 | 7 | The sidebars can be generated from the filesystem, or explicitly defined here. 8 | 9 | Create as many sidebars as you want. 10 | */ 11 | 12 | // @ts-check 13 | 14 | /** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */ 15 | const sidebars = { 16 | // By default, Docusaurus generates a sidebar from the docs folder structure 17 | tutorialSidebar: [{type: 'autogenerated', dirName: '.'}], 18 | 19 | // But you can create a sidebar manually 20 | /* 21 | tutorialSidebar: [ 22 | 'intro', 23 | 'hello', 24 | { 25 | type: 'category', 26 | label: 'Tutorial', 27 | items: ['tutorial-basics/create-a-document'], 28 | }, 29 | ], 30 | */ 31 | }; 32 | 33 | module.exports = sidebars; 34 | -------------------------------------------------------------------------------- /docs/src/components/HomepageFeatures/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import clsx from 'clsx'; 3 | import styles from './styles.module.css'; 4 | import classnames from 'classnames'; 5 | 6 | const FeatureList = [ 7 | { 8 | title: 'Customizable Environments', 9 | Svg: require('@site/static/img/emojis/openmoji_bullseye.svg').default, 10 | description: ( 11 | <> 12 | Design every aspect of your world, from the AI Agents and objects to their goals and memories. 13 | 14 | ), 15 | }, 16 | { 17 | title: 'Scalable Architecture', 18 | Svg: require('@site/static/img/emojis/1F3D7.svg').default, 19 | description: ( 20 | <> 21 | Our architecture adjusts with your needs. From WebSocket to multiple interfaces, we scale for optimal performance regardless of the task at hand. 22 | 23 | ), 24 | }, 25 | { 26 | title: 'Plug-n-Play', 27 | Svg: require('@site/static/img/emojis/openmoji_bricks.svg').default, 28 | 29 | description: ( 30 | <> 31 | A repository of ready-made memories and tools at your disposal designed to be easily deployed within your GenWorld. 32 | 33 | ), 34 | }, 35 | { 36 | title: ' Cognitive Processes', 37 | Svg: require('@site/static/img/emojis/openmoji_brain.svg').default, 38 | description: ( 39 | <> 40 | Choose the brain for your agents. From Tree of Thoughts to Chain of Thoughts and AutoGPT, each agent can think differently, aligning with their purpose. 41 | 42 | ), 43 | }, 44 | { 45 | title: 'Coordination Protocols', 46 | Svg: require('@site/static/img/emojis/openmoji_heart.svg').default, 47 | description: ( 48 | <> 49 | Pick from a range of organization processes for agent coordination, such as token-bearer or serialized processing, ensuring efficient task execution. 50 | 51 | 52 | 53 | ), 54 | }, 55 | { 56 | title: '3rd Party GenWorld Integration', 57 | Svg: require('@site/static/img/emojis/openmoji_alien_monster.svg').default, 58 | description: ( 59 | <> 60 | Leverage the power of the marketplace. Seamlessly connect existing agents and worlds to amplify your GenWorld's capabilities. 61 | 62 | ), 63 | }, 64 | ]; 65 | 66 | function Feature({Svg, title, description}) { 67 | return ( 68 |
69 |
70 | 71 |
72 |
73 |

{title}

74 |

{description}

75 |
76 |
77 | ); 78 | } 79 | 80 | export default function HomepageFeatures() { 81 | return ( 82 |
83 |
84 | 85 |
86 |

Features

87 |
88 |
89 | {FeatureList.map((props, idx) => ( 90 | 91 | ))} 92 |
93 |
94 |
95 | ); 96 | } 97 | -------------------------------------------------------------------------------- /docs/src/components/HomepageFeatures/styles.module.css: -------------------------------------------------------------------------------- 1 | .features { 2 | display: flex; 3 | align-items: center; 4 | padding-top: 2rem; 5 | width: 100%; 6 | } 7 | 8 | .featureSvg { 9 | height: 75px; 10 | width: 200px; 11 | } 12 | 13 | [data-theme='dark'] .featureSvg { 14 | filter: invert(100%); 15 | } 16 | 17 | .title { 18 | text-align: center; 19 | font-size: 3rem; 20 | font-weight: 600; 21 | padding: 2rem; 22 | } -------------------------------------------------------------------------------- /docs/src/components/HomepageQuotes/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import clsx from 'clsx'; 3 | import styles from './styles.module.css'; 4 | import classnames from 'classnames'; 5 | import Slider from "react-slick"; 6 | 7 | import "slick-carousel/slick/slick.css"; 8 | import "slick-carousel/slick/slick-theme.css"; 9 | 10 | const QuoteList = [ 11 | { 12 | name: 'Harrison Chase', 13 | avatar: "https://avatars.githubusercontent.com/u/11986836?v=4", 14 | title: 'Founder @ LangChain', 15 | quote: ( 16 | <> 17 | The Yeager.ai team keeps shipping. Really exciting to see how their GenWorlds framework brings coordinating AI agents to life built on LangChain. Coordinating Agents are a paradigm shift in GenAI and I'm pleased to see the Yeager team using LangChain to do it 18 | 19 | ), 20 | }, 21 | { 22 | name: 'Andre Zayarni', 23 | avatar: "https://avatars.githubusercontent.com/u/926368?v=4", 24 | title: 'CEO of Qdrant', 25 | quote: ( 26 | <> 27 | We are thrilled that Qdrant is Yeager's vector database and similarity search engine of choice. Now, GenWorlds' AI Agents can be trained on specific sources of data, such as Youtube transcripts. It is fantastic to see how they optimize LLM context windows by using Qdrant vector stores for long-term memory and LLMs for short-term memory. This is exactly how we envisioned our platform would be used in robust GenAI Applications. 28 | 29 | ), 30 | }, 31 | ]; 32 | 33 | const Quote = ({ avatar, name, title, quote }) => ( 34 |
35 |
36 |
37 | {name} 38 |
39 |
{name}
40 | 41 | {title} 42 | 43 |
44 |
45 |
46 | {quote} 47 |
48 |
49 |
50 | ); 51 | 52 | 53 | export default function HomepageQuotes() { 54 | var settings = { 55 | dots: true, 56 | infinite: true, 57 | speed: 5000, 58 | slidesToShow: 1, 59 | slidesToScroll: 1, 60 | initialSlide: 1, 61 | autoplay: true, 62 | }; 63 | 64 | return ( 65 | 66 |
67 |
68 | 69 | {QuoteList.map((props, idx) => ( 70 | 71 | ))} 72 | 73 |
74 |
75 | ); 76 | } 77 | -------------------------------------------------------------------------------- /docs/src/components/HomepageQuotes/styles.module.css: -------------------------------------------------------------------------------- 1 | .quotesContainer { 2 | margin-left: auto; 3 | margin-right: auto; 4 | margin-top: auto; 5 | margin-bottom: auto; 6 | } 7 | .quoteAvatar{ 8 | margin-left: auto; 9 | margin-right: auto; 10 | } 11 | 12 | .features { 13 | display: flex; 14 | align-items: center; 15 | padding: 2rem 0; 16 | width: 100%; 17 | max-width: 1280px; 18 | align-self: center; 19 | text-align: center; 20 | } 21 | -------------------------------------------------------------------------------- /docs/src/css/custom.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Any CSS included here will be global. The classic template 3 | * bundles Infima by default. Infima is a CSS framework designed to 4 | * work well for content-centric websites. 5 | */ 6 | 7 | @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;700;800&display=swap'); 8 | 9 | /* You can override the default Infima variables here. */ 10 | :root { 11 | --ifm-color-primary: #ff7a59; 12 | --ifm-color-primary-dark: #fa6d50; 13 | --ifm-color-primary-darker: #f8664c; 14 | --ifm-color-primary-darkest: #f1523f; 15 | --ifm-color-primary-light: #ff8761; 16 | --ifm-color-primary-lighter: #ff8e66; 17 | --ifm-color-primary-lightest: #ffa272; 18 | --ifm-code-font-size: 95%; 19 | --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1); 20 | --ifm-font-family-base: 'Poppins', sans-serif; 21 | } 22 | 23 | 24 | /* For readability concerns, you should choose a lighter palette in dark mode. */ 25 | [data-theme='dark'] { 26 | --ifm-color-primary: #ff7a59; 27 | --ifm-color-primary-dark: #fa6d50; 28 | --ifm-color-primary-darker: #f8664c; 29 | --ifm-color-primary-darkest: #f1523f; 30 | --ifm-color-primary-light: #ff8761; 31 | --ifm-color-primary-lighter: #ff8e66; 32 | --ifm-color-primary-lightest: #ffa272; 33 | --ifm-code-font-size: 95%; 34 | --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1); 35 | --ifm-font-family-base: 'Poppins', sans-serif; 36 | } 37 | 38 | .hero--primary { 39 | --ifm-hero-background-color: #f8f6ea; 40 | color: black !important; 41 | } 42 | 43 | [data-theme='dark'] .hero--primary { 44 | --ifm-hero-background-color: #1b1b1d; 45 | color: white !important; 46 | } -------------------------------------------------------------------------------- /docs/src/pages/index.module.css: -------------------------------------------------------------------------------- 1 | /* stylelint-disable docusaurus/copyright-header */ 2 | 3 | /** 4 | * CSS files with the .module.css suffix will be treated as CSS modules 5 | * and scoped locally. 6 | */ 7 | 8 | .heroBanner { 9 | padding: 4rem 0; 10 | text-align: center; 11 | position: relative; 12 | overflow: hidden; 13 | } 14 | 15 | .heroText{ 16 | padding: 4rem; 17 | margin-top: auto; 18 | margin-bottom: auto; 19 | } 20 | .heroCard{ 21 | color: #080808; 22 | padding: 1rem; 23 | } 24 | 25 | [data-theme='dark'] .heroCard { 26 | color: white; 27 | } 28 | 29 | .buttons { 30 | display: flex; 31 | align-items: center; 32 | justify-content: center; 33 | } 34 | 35 | .content { 36 | display: flex; 37 | flex-direction: column; 38 | } 39 | 40 | .container .title { 41 | font-size: 3rem; 42 | font-weight: 600; 43 | margin-bottom: 1rem; 44 | } 45 | 46 | .features { 47 | display: flex; 48 | align-items: center; 49 | padding: 2rem 0; 50 | width: 100%; 51 | max-width: 1280px; 52 | align-self: center; 53 | text-align: center; 54 | } 55 | 56 | .description { 57 | display: flex; 58 | align-items: center; 59 | padding: 4rem 0 0; 60 | width: 100%; 61 | max-width: 1280px; 62 | align-self: center; 63 | } 64 | 65 | .featureImage { 66 | height: 200px; 67 | width: 200px; 68 | } 69 | 70 | .demoImg { 71 | max-height: 300px; 72 | } 73 | 74 | .descriptionRow { 75 | justify-content: center; 76 | align-items: center; 77 | margin-left: unset !important; 78 | margin-right: unset !important; 79 | } 80 | 81 | .imgColumn { 82 | display: flex !important; 83 | justify-content: center; 84 | } 85 | 86 | .column { 87 | flex-direction: column; 88 | padding-bottom: 4rem; 89 | } 90 | 91 | .videoContainer { 92 | position: relative; 93 | width: 50%; 94 | min-width: 640px; 95 | align-self: center; 96 | } 97 | 98 | .videoContainer::after { 99 | padding-top: 53.57%; 100 | display: block; 101 | content: ''; 102 | } 103 | 104 | .videoContainer iframe { 105 | position: absolute; 106 | top: 0; 107 | left: 0; 108 | width: 100%; 109 | height: 100%; 110 | } 111 | 112 | .caption { 113 | font-weight: 600; 114 | margin-top: 1rem; 115 | } 116 | 117 | .iframe { 118 | aspect-ratio: 16 / 9; 119 | max-width: 100% !important; 120 | width: 100% !important; 121 | height: auto !important; 122 | padding: 0; 123 | margin: 0; 124 | } 125 | 126 | .communityHeader{ 127 | padding: 2rem 0 0; 128 | width: 100%; 129 | max-width: 1280px; 130 | align-self: center; 131 | text-align: center; 132 | margin-left: auto; 133 | margin-right: auto; 134 | } 135 | 136 | .quotesContainer { 137 | margin-left: auto; 138 | margin-right: auto; 139 | margin-top: auto; 140 | margin-bottom: auto; 141 | } 142 | 143 | .quickLinks { 144 | align-items: center; 145 | width: 100%; 146 | align-self: left; 147 | text-align: left; 148 | } 149 | 150 | .quickLinks li { 151 | list-style-type: none !important; 152 | padding: 0.5em; 153 | font-weight: 500; 154 | } 155 | 156 | @media screen and (max-width: 966px) { 157 | .heroBanner { 158 | padding: 2rem; 159 | } 160 | 161 | .description { 162 | text-align: center; 163 | } 164 | 165 | .videoContainer { 166 | min-width: unset; 167 | width: calc(100% - 2rem); 168 | } 169 | 170 | .caption { 171 | margin-top: 0.5rem; 172 | } 173 | } -------------------------------------------------------------------------------- /docs/src/pages/markdown-page.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Markdown page example 3 | --- 4 | 5 | # Markdown page example 6 | 7 | You don't need React to write simple standalone pages. 8 | -------------------------------------------------------------------------------- /docs/static/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yeagerai/genworlds/36ab16049a4c215a646ce2a303e1fb90822e193b/docs/static/.nojekyll -------------------------------------------------------------------------------- /docs/static/img/GenWorlds-Platform.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yeagerai/genworlds/36ab16049a4c215a646ce2a303e1fb90822e193b/docs/static/img/GenWorlds-Platform.png -------------------------------------------------------------------------------- /docs/static/img/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yeagerai/genworlds/36ab16049a4c215a646ce2a303e1fb90822e193b/docs/static/img/android-chrome-192x192.png -------------------------------------------------------------------------------- /docs/static/img/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yeagerai/genworlds/36ab16049a4c215a646ce2a303e1fb90822e193b/docs/static/img/android-chrome-512x512.png -------------------------------------------------------------------------------- /docs/static/img/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yeagerai/genworlds/36ab16049a4c215a646ce2a303e1fb90822e193b/docs/static/img/apple-touch-icon.png -------------------------------------------------------------------------------- /docs/static/img/dna.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/static/img/docusaurus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yeagerai/genworlds/36ab16049a4c215a646ce2a303e1fb90822e193b/docs/static/img/docusaurus.png -------------------------------------------------------------------------------- /docs/static/img/emojis/1F3D7.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /docs/static/img/emojis/E202.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /docs/static/img/emojis/E249.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /docs/static/img/emojis/openmoji_alien_monster.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /docs/static/img/emojis/openmoji_brain.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /docs/static/img/emojis/openmoji_bricks.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /docs/static/img/emojis/openmoji_bullseye.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /docs/static/img/emojis/openmoji_heart.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /docs/static/img/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yeagerai/genworlds/36ab16049a4c215a646ce2a303e1fb90822e193b/docs/static/img/favicon-16x16.png -------------------------------------------------------------------------------- /docs/static/img/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yeagerai/genworlds/36ab16049a4c215a646ce2a303e1fb90822e193b/docs/static/img/favicon-32x32.png -------------------------------------------------------------------------------- /docs/static/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yeagerai/genworlds/36ab16049a4c215a646ce2a303e1fb90822e193b/docs/static/img/favicon.ico -------------------------------------------------------------------------------- /docs/static/img/genworlds-social-card.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yeagerai/genworlds/36ab16049a4c215a646ce2a303e1fb90822e193b/docs/static/img/genworlds-social-card.png -------------------------------------------------------------------------------- /docs/static/img/genworlds_sample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yeagerai/genworlds/36ab16049a4c215a646ce2a303e1fb90822e193b/docs/static/img/genworlds_sample.png -------------------------------------------------------------------------------- /docs/static/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yeagerai/genworlds/36ab16049a4c215a646ce2a303e1fb90822e193b/docs/static/img/logo.png -------------------------------------------------------------------------------- /docs/static/img/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/static/img/logo_with_text_white.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yeagerai/genworlds/36ab16049a4c215a646ce2a303e1fb90822e193b/docs/static/img/logo_with_text_white.webp -------------------------------------------------------------------------------- /docs/static/img/site.webmanifest: -------------------------------------------------------------------------------- 1 | {"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"} -------------------------------------------------------------------------------- /genworlds/__init__.py: -------------------------------------------------------------------------------- 1 | __version__='0.0.18' -------------------------------------------------------------------------------- /genworlds/agents/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yeagerai/genworlds/36ab16049a4c215a646ce2a303e1fb90822e193b/genworlds/agents/__init__.py -------------------------------------------------------------------------------- /genworlds/agents/abstracts/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yeagerai/genworlds/36ab16049a4c215a646ce2a303e1fb90822e193b/genworlds/agents/abstracts/__init__.py -------------------------------------------------------------------------------- /genworlds/agents/abstracts/action_planner.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | from typing import List, Dict, Any, Tuple 3 | from genworlds.agents.abstracts.thought import AbstractThought 4 | from genworlds.agents.abstracts.agent_state import AbstractAgentState 5 | from genworlds.events.abstracts.event import AbstractEvent 6 | 7 | 8 | class AbstractActionPlanner(ABC): 9 | def __init__( 10 | self, 11 | action_schema_selector: AbstractThought, 12 | event_filler: AbstractThought, 13 | other_thoughts: List[ 14 | AbstractThought 15 | ] = [], # other trigger event param filler thoughts 16 | ): 17 | self.action_schema_selector = action_schema_selector 18 | self.event_filler = event_filler 19 | self.other_thoughts = other_thoughts 20 | 21 | def plan_next_action(self, state: AbstractAgentState) -> Tuple[str, AbstractEvent]: 22 | if len(state.current_action_chain) > 0: 23 | action_schema = state.current_action_chain.pop(0) 24 | trigger_event = self.fill_triggering_event(action_schema, state) 25 | return action_schema, trigger_event 26 | action_schema = self.select_next_action_schema(state) 27 | trigger_event = self.fill_triggering_event(action_schema, state) 28 | return action_schema, trigger_event 29 | 30 | @abstractmethod 31 | def select_next_action_schema(self, state: AbstractAgentState) -> str: 32 | """Select the next action schema based on the given state. 33 | 34 | :param state: The state based on which the next action schema should be selected. 35 | :return: The action schema, e.g., "MoveTo". 36 | """ 37 | pass 38 | 39 | @abstractmethod 40 | def fill_triggering_event( 41 | self, next_action_schema: str, state: AbstractAgentState 42 | ) -> Dict[str, Any]: 43 | """Fill the triggering event parameters based on the given state. 44 | 45 | :param state: The state based on which the triggering event parameters should be filled. 46 | :return: The triggering event parameters, e.g., {"arg1": x, "arg2": y, ...}. 47 | """ 48 | pass 49 | -------------------------------------------------------------------------------- /genworlds/agents/abstracts/agent.py: -------------------------------------------------------------------------------- 1 | import traceback 2 | from time import sleep 3 | import threading 4 | from typing import List, Type 5 | 6 | from genworlds.agents.utils.validate_action import validate_action 7 | from genworlds.agents.abstracts.action_planner import AbstractActionPlanner 8 | from genworlds.agents.abstracts.state_manager import AbstractStateManager 9 | from genworlds.events.abstracts.action import AbstractAction 10 | from genworlds.objects.abstracts.object import AbstractObject 11 | 12 | 13 | class AbstractAgent(AbstractObject): 14 | """Abstract interface class for an Agent. 15 | 16 | This class represents an abstract agent that can think and perform actions 17 | within a simulation environment. 18 | """ 19 | 20 | def __init__( 21 | self, 22 | name: str, 23 | id: str, 24 | description: str, 25 | state_manager: Type[AbstractStateManager], 26 | action_planner: Type[AbstractActionPlanner], 27 | host_world_id: str = None, 28 | actions: List[type[AbstractAction]] = [], 29 | ): 30 | self.action_planner = action_planner 31 | self.state_manager = state_manager 32 | super().__init__(name, id, description, host_world_id, actions) 33 | 34 | def think_n_do(self): 35 | """Continuously plans and executes actions based on the agent's state.""" 36 | while True: 37 | try: 38 | if self.state_manager.state.is_asleep: 39 | sleep(1) 40 | continue 41 | else: 42 | sleep( 43 | 0.5 44 | ) # actions that its output take over 0.5 secs have to implement a sleep in the action before finishing 45 | state = self.state_manager.get_updated_state() 46 | sleep(0.5) 47 | action_schema, trigger_event = self.action_planner.plan_next_action( 48 | state 49 | ) 50 | 51 | if action_schema.startswith(self.id): 52 | selected_action = [ 53 | action 54 | for action in self.actions 55 | if action.action_schema[0] == action_schema 56 | ][0] 57 | selected_action(trigger_event) 58 | else: 59 | self.send_event(trigger_event) 60 | except Exception as e: 61 | print(f"Error in think_n_do: {e}") 62 | traceback.print_exc() 63 | 64 | def launch(self): 65 | """Launches the agent by starting the websocket and thinking threads.""" 66 | self.launch_websocket_thread() 67 | sleep(0.1) 68 | thinking_thread = threading.Thread( 69 | target=self.think_n_do, 70 | name=f"Agent {self.state_manager.state.id} Thinking Thread", 71 | daemon=True, 72 | ) 73 | thinking_thread.start() 74 | -------------------------------------------------------------------------------- /genworlds/agents/abstracts/agent_state.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, List, Set, Any, Optional 2 | from pydantic import BaseModel, Field 3 | from genworlds.events.abstracts.event import AbstractEvent 4 | 5 | 6 | class AbstractAgentState(BaseModel): 7 | """ 8 | ### Serialize to JSON 9 | json_representation = instance.json() 10 | 11 | ### Deserialize from JSON 12 | instance = AbstractAgentState.parse_raw(json_representation) 13 | """ 14 | 15 | # Static initialized in the constructor 16 | id: str = Field(..., description="Unique identifier of the agent.") 17 | description: str = Field(..., description="Description of the agent.") 18 | name: str = Field(..., description="Name of the agent.") 19 | host_world_prompt: str = Field(..., description="Prompt of the host world.") 20 | simulation_memory_persistent_path: Optional[str] = Field( 21 | None, description="Memory object storing the simulation data." 22 | ) 23 | memory_ignored_event_types: Set[str] = Field( 24 | ..., 25 | description="Set of event types that will be ignored and not added to memory of the agent.", 26 | ) 27 | wakeup_event_types: Set[str] = Field( 28 | ..., description="Events that can wake up the agent." 29 | ) 30 | action_schema_chains: List[List[str]] = Field( 31 | ..., 32 | description="List of action schema chains that inhibit the action selector.", 33 | ) 34 | goals: List[str] = Field(..., description="List of goals of the agent.") 35 | 36 | # Dynamically updated, so during one think_n_do cycle all of these must be updated somehow 37 | plan: List[str] = Field(..., description="List of actions that form the plan.") 38 | last_retrieved_memory: str = Field( 39 | ..., description="Last retrieved memory of the agent." 40 | ) 41 | other_thoughts_filled_parameters: Dict[str, str] = Field( 42 | ..., description="Parameters filled by other thoughts." 43 | ) 44 | available_action_schemas: Dict[str, Any] = Field( 45 | ..., description="Available action schemas with their descriptions." 46 | ) 47 | available_entities: List[str] = Field( 48 | ..., description="List of available entities in the environment." 49 | ) 50 | is_asleep: bool = Field(..., description="Indicates whether the agent is asleep.") 51 | current_action_chain: List[str] = Field( 52 | ..., description="List of action schemas that are currently being executed." 53 | ) 54 | -------------------------------------------------------------------------------- /genworlds/agents/abstracts/state_manager.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | from genworlds.agents.abstracts.agent_state import AbstractAgentState 3 | 4 | 5 | class AbstractStateManager(ABC): 6 | state: AbstractAgentState 7 | 8 | @abstractmethod 9 | def get_updated_state(self) -> AbstractAgentState: 10 | """Retrieve the updated state""" 11 | pass 12 | -------------------------------------------------------------------------------- /genworlds/agents/abstracts/thought.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | 3 | 4 | class AbstractThought(ABC): 5 | @abstractmethod 6 | def run(self, llm_params: dict) -> str: 7 | """Run the brain with the given parameters and produce a response.""" 8 | -------------------------------------------------------------------------------- /genworlds/agents/abstracts/thought_action.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, Tuple 2 | from genworlds.events.abstracts.action import AbstractAction 3 | from genworlds.agents.abstracts.thought import AbstractThought 4 | 5 | 6 | class ThoughtAction(AbstractAction): 7 | """ 8 | Abstract interface class for a Thought Action. 9 | 10 | This includes the list of thoughts required to fill the parameters of the trigger event. 11 | """ 12 | 13 | # {parameter_name: [thought_class, run_dict]} 14 | required_thoughts: Dict[str, Tuple[AbstractThought, dict]] 15 | 16 | # in the __call__ method after executing the action you must clean the state 17 | # with self.host_object.state_manager.state.other_thoughts_filled_parameters = {} 18 | -------------------------------------------------------------------------------- /genworlds/agents/concrete/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yeagerai/genworlds/36ab16049a4c215a646ce2a303e1fb90822e193b/genworlds/agents/concrete/__init__.py -------------------------------------------------------------------------------- /genworlds/agents/concrete/basic_assistant/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yeagerai/genworlds/36ab16049a4c215a646ce2a303e1fb90822e193b/genworlds/agents/concrete/basic_assistant/__init__.py -------------------------------------------------------------------------------- /genworlds/agents/concrete/basic_assistant/action_planner.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, Any, List 2 | import json 3 | from datetime import datetime 4 | from genworlds.events.abstracts.event import AbstractEvent 5 | from genworlds.agents.abstracts.action_planner import AbstractActionPlanner 6 | from genworlds.agents.abstracts.agent_state import AbstractAgentState 7 | from genworlds.agents.abstracts.thought import AbstractThought 8 | from genworlds.agents.abstracts.agent import AbstractAgent 9 | from genworlds.agents.concrete.basic_assistant.thoughts.action_schema_selector import ( 10 | ActionSchemaSelectorThought, 11 | ) 12 | from genworlds.agents.concrete.basic_assistant.thoughts.event_filler import ( 13 | EventFillerThought, 14 | ) 15 | from genworlds.agents.abstracts.thought_action import ThoughtAction 16 | from genworlds.utils.schema_to_model import json_schema_to_pydantic_model 17 | 18 | 19 | class BasicAssistantActionPlanner(AbstractActionPlanner): 20 | """This action planner selects the action schema with the highest priority and fills the event parameters with the highest priority.""" 21 | 22 | def __init__( 23 | self, 24 | host_agent: AbstractAgent, 25 | openai_api_key, 26 | initial_agent_state: AbstractAgentState, 27 | other_thoughts: List[AbstractThought] = [], 28 | model_name: str = "gpt-3.5-turbo-1106", 29 | ): 30 | self.host_agent = host_agent 31 | action_schema_selector = ActionSchemaSelectorThought( 32 | openai_api_key=openai_api_key, 33 | agent_state=initial_agent_state, 34 | model_name=model_name, 35 | ) 36 | event_filler = EventFillerThought( 37 | openai_api_key=openai_api_key, 38 | agent_state=initial_agent_state, 39 | model_name=model_name, 40 | ) 41 | other_thoughts = other_thoughts 42 | super().__init__( 43 | action_schema_selector, 44 | event_filler, 45 | other_thoughts, 46 | ) 47 | 48 | def select_next_action_schema(self, state: AbstractAgentState) -> str: 49 | # call action_schema_selector thought with the correct parameters 50 | ( 51 | next_action_schema, 52 | updated_plan, 53 | ) = self.action_schema_selector.run() # gives enum values 54 | state.plan = updated_plan 55 | if next_action_schema in [el[0] for el in state.action_schema_chains]: 56 | state.current_action_chain = state.action_schema_chains[ 57 | [el[0] for el in state.action_schema_chains].index(next_action_schema) 58 | ][1:] 59 | return next_action_schema 60 | 61 | def fill_triggering_event( 62 | self, next_action_schema: str, state: AbstractAgentState 63 | ) -> Dict[str, Any]: 64 | # check if is a thought action and compute the missing parameters 65 | if next_action_schema.startswith(self.host_agent.id): 66 | next_action = [ 67 | action 68 | for action in self.host_agent.actions 69 | if next_action_schema == action.action_schema[0] 70 | ][0] 71 | trigger_event_class = next_action.trigger_event_class 72 | if isinstance(next_action, ThoughtAction): 73 | for param in next_action.required_thoughts: 74 | thought_class = next_action.required_thoughts[param] 75 | thought = thought_class(self.host_agent.state_manager.state) 76 | state.other_thoughts_filled_parameters[param] = thought.run() 77 | 78 | else: 79 | trigger_event_class_schema = json.loads( 80 | self.host_agent.state_manager.state.available_action_schemas[ 81 | next_action_schema 82 | ].split("|")[-1] 83 | ) 84 | trigger_event_class = json_schema_to_pydantic_model( 85 | trigger_event_class_schema 86 | ) 87 | 88 | trigger_event: AbstractEvent = self.event_filler.run(trigger_event_class) 89 | trigger_event.created_at = datetime.now().isoformat() 90 | return trigger_event 91 | -------------------------------------------------------------------------------- /genworlds/agents/concrete/basic_assistant/actions.py: -------------------------------------------------------------------------------- 1 | import json 2 | from genworlds.objects.abstracts.object import AbstractObject 3 | from genworlds.events.abstracts.action import AbstractAction 4 | from genworlds.events.abstracts.event import AbstractEvent 5 | from genworlds.worlds.concrete.base.actions import ( 6 | WorldSendsAvailableEntitiesEvent, 7 | WorldSendsAvailableActionSchemasEvent, 8 | ) 9 | 10 | 11 | class UpdateAgentAvailableEntities(AbstractAction): 12 | trigger_event_class = WorldSendsAvailableEntitiesEvent 13 | description = "Update the available entities in the agent's state." 14 | 15 | def __init__(self, host_object: AbstractObject): 16 | super().__init__(host_object=host_object) 17 | 18 | def __call__(self, event: WorldSendsAvailableEntitiesEvent): 19 | self.host_object.state_manager.state.available_entities = ( 20 | event.available_entities 21 | ) 22 | 23 | 24 | class UpdateAgentAvailableActionSchemas(AbstractAction): 25 | trigger_event_class = WorldSendsAvailableActionSchemasEvent 26 | description = "Update the available action schemas in the agent's state." 27 | 28 | def __init__(self, host_object: AbstractObject): 29 | super().__init__(host_object=host_object) 30 | 31 | def __call__(self, event: WorldSendsAvailableActionSchemasEvent): 32 | self.host_object.state_manager.state.available_action_schemas = ( 33 | event.available_action_schemas 34 | ) 35 | 36 | 37 | class AgentWantsToSleepEvent(AbstractEvent): 38 | event_type = "agent_wants_to_sleep" 39 | description = "The agent wants to sleep. He has to wait for new events. The sender and the target ID is the agent's ID." 40 | 41 | 42 | class AgentGoesToSleepEvent(AbstractEvent): 43 | event_type = "agent_goes_to_sleep" 44 | description = "The agent is waiting." 45 | 46 | 47 | class AgentGoesToSleep(AbstractAction): 48 | trigger_event_class = AgentWantsToSleepEvent 49 | description = "The agent goes to sleep. He has to wait for new events. The sender and the target ID is the agent's ID." 50 | 51 | def __init__(self, host_object: AbstractObject): 52 | super().__init__(host_object=host_object) 53 | 54 | def __call__(self, event: AgentWantsToSleepEvent): 55 | self.host_object.state_manager.state.is_asleep = True 56 | self.host_object.state_manager.state.plan = [] 57 | self.host_object.send_event( 58 | AgentGoesToSleepEvent(sender_id=self.host_object.id, target_id=None) 59 | ) 60 | print("Agent goes to sleep...") 61 | 62 | 63 | class WildCardEvent(AbstractEvent): 64 | event_type = "*" 65 | description = "This event is used as a master listener for all events." 66 | 67 | 68 | class AgentListensEvents(AbstractAction): 69 | trigger_event_class = WildCardEvent 70 | description = "The agent listens to all the events and stores them in his memory." 71 | 72 | def __init__(self, host_object: AbstractObject): 73 | super().__init__(host_object=host_object) 74 | 75 | def __call__(self, event: dict): 76 | if ( 77 | event["target_id"] == self.host_object.id 78 | or event["target_id"] == None 79 | or event["sender_id"] == self.host_object.id 80 | ): 81 | if ( 82 | event["event_type"] 83 | not in self.host_object.state_manager.state.memory_ignored_event_types 84 | ): 85 | self.host_object.state_manager.memory.add_event( 86 | json.dumps(event), summarize=False 87 | ) # takes time 88 | if ( 89 | event["event_type"] 90 | in self.host_object.state_manager.state.wakeup_event_types 91 | ): 92 | self.host_object.state_manager.state.is_asleep = False 93 | print("Agent is waking up...") 94 | 95 | 96 | class AgentSpeaksWithUserTriggerEvent(AbstractEvent): 97 | event_type = "agent_speaks_with_user_trigger_event" 98 | description = "An agent speaks with the user." 99 | message: str 100 | 101 | 102 | class AgentSpeaksWithUserEvent(AbstractEvent): 103 | event_type = "agent_speaks_with_user_event" 104 | description = "An agent speaks with the user." 105 | message: str 106 | 107 | 108 | class AgentSpeaksWithUser(AbstractAction): 109 | trigger_event_class = AgentSpeaksWithUserTriggerEvent 110 | description = "An agent speaks with the user." 111 | 112 | def __init__(self, host_object: AbstractObject): 113 | super().__init__(host_object=host_object) 114 | 115 | def __call__(self, event: AgentSpeaksWithUserTriggerEvent): 116 | self.host_object.send_event( 117 | AgentSpeaksWithUserEvent( 118 | sender_id=self.host_object.id, 119 | target_id=event.target_id, 120 | message=event.message, 121 | ) 122 | ) 123 | 124 | 125 | class AgentSpeaksWithAgentEvent(AbstractEvent): 126 | event_type = "agent_speaks_with_agent_event" 127 | description = "An agent speaks with another agent." 128 | message: str 129 | 130 | 131 | class AgentSpeaksWithAgent(AbstractAction): 132 | trigger_event_class = AgentSpeaksWithAgentEvent 133 | description = "An agent speaks with another agent." 134 | 135 | def __init__(self, host_object: AbstractObject): 136 | super().__init__(host_object=host_object) 137 | 138 | def __call__(self, event: AgentSpeaksWithAgentEvent): 139 | self.host_object.send_event(event) 140 | -------------------------------------------------------------------------------- /genworlds/agents/concrete/basic_assistant/agent.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | from genworlds.agents.abstracts.agent import AbstractAgent 3 | from genworlds.events.abstracts.event import AbstractEvent 4 | from genworlds.agents.abstracts.agent_state import AbstractAgentState 5 | from genworlds.agents.concrete.basic_assistant.state_manager import ( 6 | BasicAssistantStateManager, 7 | ) 8 | from genworlds.agents.concrete.basic_assistant.action_planner import ( 9 | BasicAssistantActionPlanner, 10 | ) 11 | from genworlds.events.abstracts.action import AbstractAction 12 | from genworlds.agents.concrete.basic_assistant.actions import ( 13 | UpdateAgentAvailableEntities, 14 | UpdateAgentAvailableActionSchemas, 15 | AgentGoesToSleep, 16 | AgentListensEvents, 17 | AgentSpeaksWithUser, 18 | AgentSpeaksWithAgent, 19 | ) 20 | from genworlds.agents.abstracts.thought import AbstractThought 21 | 22 | 23 | class BasicAssistant(AbstractAgent): 24 | def __init__( 25 | self, 26 | openai_api_key: str, 27 | name: str, 28 | id: str, 29 | description: str, 30 | host_world_id: str = None, 31 | initial_agent_state: AbstractAgentState = None, 32 | action_classes: List[type[AbstractAction]] = [], 33 | other_thoughts: List[AbstractThought] = [], 34 | model_name: str = "gpt-3.5-turbo-1106", 35 | ): 36 | state_manager = BasicAssistantStateManager( 37 | self, initial_agent_state, openai_api_key 38 | ) 39 | action_planner = BasicAssistantActionPlanner( 40 | openai_api_key=openai_api_key, 41 | initial_agent_state=state_manager.state, 42 | other_thoughts=other_thoughts, 43 | model_name=model_name, 44 | host_agent=self, 45 | ) 46 | 47 | actions = [] 48 | for action_class in action_classes: 49 | actions.append(action_class(host_object=self)) 50 | 51 | actions.append(UpdateAgentAvailableEntities(host_object=self)) 52 | actions.append(UpdateAgentAvailableActionSchemas(host_object=self)) 53 | actions.append(AgentGoesToSleep(host_object=self)) 54 | actions.append(AgentListensEvents(host_object=self)) 55 | actions.append(AgentSpeaksWithUser(host_object=self)) 56 | actions.append(AgentSpeaksWithAgent(host_object=self)) 57 | 58 | super().__init__( 59 | name, id, description, state_manager, action_planner, host_world_id, actions 60 | ) 61 | 62 | def add_wakeup_event(self, event_class: AbstractEvent): 63 | self.state_manager.state.wakeup_event_types.add( 64 | event_class.__fields__["event_type"].default 65 | ) 66 | 67 | def add_memory_ignored_event(self, event_type: str): 68 | self.state_manager.state.memory_ignored_event_types.add(event_type) 69 | -------------------------------------------------------------------------------- /genworlds/agents/concrete/basic_assistant/state_manager.py: -------------------------------------------------------------------------------- 1 | from time import sleep 2 | from genworlds.agents.abstracts.state_manager import AbstractStateManager 3 | from genworlds.agents.abstracts.agent_state import AbstractAgentState 4 | from genworlds.agents.abstracts.agent import AbstractAgent 5 | 6 | from genworlds.worlds.concrete.base.actions import AgentWantsUpdatedStateEvent 7 | from genworlds.agents.memories.simulation_memory import SimulationMemory 8 | 9 | 10 | class BasicAssistantStateManager(AbstractStateManager): 11 | """This state manager keeps track of the current state of the agent.""" 12 | 13 | def __init__( 14 | self, host_agent: AbstractAgent, state: AbstractAgentState, openai_api_key: str 15 | ): 16 | self.host_agent = host_agent 17 | self.state = state 18 | if not state: 19 | self.state = self._initialize_state(host_agent) 20 | else: 21 | self.state = state 22 | 23 | self.memory = SimulationMemory(openai_api_key=openai_api_key) 24 | 25 | def _initialize_state( 26 | self, 27 | ) -> ( 28 | AbstractAgentState 29 | ): # should trigger an action to get the initial state from the world 30 | return AbstractAgentState( 31 | name=self.host_agent.name, 32 | id=self.host_agent.id, 33 | goals=[ 34 | "Starts waiting and sleeps till the user starts a new question.", 35 | f"Once {self.host_agent.name} receives a user's question, he makes sure to have all the information before sending the answer to the user.", 36 | f"When {self.host_agent.name} has all the required information, he speaks to the user with the results through the agent_speaks_with_user_event.", 37 | "After sending the response, he waits for the next user question.", 38 | "If you have been waiting for any object or entity to send you an event for over 30 seconds, you will wait and sleep until you receive a new event.", 39 | ], 40 | available_entities={}, 41 | available_action_schemas={}, 42 | current_action_chain=[], 43 | host_world_prompt="", 44 | is_asleep=False, 45 | simulation_memory_persistent_path="./", 46 | important_event_types=set(), # fill 47 | interesting_event_types=set(), # fill 48 | wakeup_event_types=set(), # fill 49 | action_schema_chains=[], 50 | ) 51 | 52 | def get_updated_state(self) -> AbstractAgentState: 53 | self.host_agent.send_event( 54 | AgentWantsUpdatedStateEvent( 55 | sender_id=self.host_agent.id, target_id=self.host_agent.host_world_id 56 | ) 57 | ) 58 | # retrieve memory and update last_retrieved_memory 59 | query = "No plan" if self.state.plan == [] else str(self.state.plan) 60 | self.host_agent.state_manager.state.last_retrieved_memory = ( 61 | self.memory.get_event_stream_memories(query=query) 62 | ) 63 | # meanwhile the concrete.base world processes the request and triggers the basic_assistant actions that update the state 64 | return self.state 65 | -------------------------------------------------------------------------------- /genworlds/agents/concrete/basic_assistant/thoughts/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yeagerai/genworlds/36ab16049a4c215a646ce2a303e1fb90822e193b/genworlds/agents/concrete/basic_assistant/thoughts/__init__.py -------------------------------------------------------------------------------- /genworlds/agents/concrete/basic_assistant/thoughts/action_schema_selector.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | from genworlds.agents.abstracts.agent_state import AbstractAgentState 3 | from genworlds.agents.abstracts.thought import AbstractThought 4 | from langchain.chat_models import ChatOpenAI 5 | from enum import Enum 6 | from pydantic import BaseModel, Field 7 | from langchain.chains.openai_functions import ( 8 | create_structured_output_chain, 9 | ) 10 | from langchain.chat_models import ChatOpenAI 11 | from langchain.prompts import ChatPromptTemplate 12 | 13 | 14 | class ActionSchemaSelectorThought(AbstractThought): 15 | def __init__( 16 | self, 17 | agent_state: AbstractAgentState, 18 | openai_api_key: str, 19 | model_name: str = "gpt-3.5-turbo-1106", 20 | ): 21 | self.agent_state = agent_state 22 | self.model_name = model_name 23 | self.llm = ChatOpenAI( 24 | model=self.model_name, openai_api_key=openai_api_key, temperature=0.1 25 | ) 26 | 27 | def run(self): 28 | class PlanNextAction(BaseModel): 29 | """Plans for the next action to be executed by the agent.""" 30 | 31 | action_name: str = Field( 32 | ..., 33 | description="Selects the action name of the next action to be executed from the list of available action names.", 34 | ) 35 | is_action_valid: bool = Field( 36 | ..., description="Determines whether the next action is valid or not." 37 | ) 38 | is_action_valid_reason: str = Field( 39 | ..., 40 | description="Then explains the rationale of whether it is valid or not valid action.", 41 | ) 42 | new_plan: List[str] = Field( 43 | ..., description="The new plan to execute to achieve the goals." 44 | ) 45 | 46 | action_schemas_full_string = "## Available Actions: \n\n" 47 | for ( 48 | action_schema_key, 49 | action_schema_value, 50 | ) in self.agent_state.available_action_schemas.items(): 51 | action_schemas_full_string += ( 52 | "Action Name: " 53 | + action_schema_key 54 | + "\nAction Description: " 55 | + action_schema_value.split("|")[0] 56 | + "\n\n" 57 | ) 58 | 59 | prompt = ChatPromptTemplate.from_messages( 60 | [ 61 | ("system", "You are {agent_name}, {agent_description}.\n"), 62 | ( 63 | "system", 64 | "You are embedded in a simulated world with those properties {agent_world_state}\n", 65 | ), 66 | ("system", "Those are your goals: \n{goals}\n"), 67 | ( 68 | "system", 69 | "And this is the previous plan to achieve the goals: \n{plan}\n", 70 | ), 71 | ( 72 | "system", 73 | "Here is your memories of all the events that you remember from being in this simulation: \n{memory}\n", 74 | ), 75 | ( 76 | "system", 77 | "Those are the available actions that you can choose from: \n{available_actions}\n", 78 | ), 79 | ("human", "{footer}\n"), 80 | ] 81 | ) 82 | 83 | chain = create_structured_output_chain( 84 | PlanNextAction.schema(), self.llm, prompt, verbose=True 85 | ) 86 | 87 | response = chain.run( 88 | agent_name=self.agent_state.name, 89 | agent_description=self.agent_state.description, 90 | agent_world_state=self.agent_state.host_world_prompt, 91 | goals=self.agent_state.goals, 92 | plan=self.agent_state.plan, 93 | memory=self.agent_state.last_retrieved_memory, 94 | available_actions=action_schemas_full_string, 95 | footer="""Select the next action which must be a value of the available actions that you can choose from based on previous context. 96 | Also select whether the action is valid or not, and if not, why. 97 | And finally, state a new updated plan that you want to execute to achieve your goals. If your next action is going to sleep, then you don't need to state a new plan. 98 | """, 99 | ) 100 | response = PlanNextAction.parse_obj(response) 101 | return response.action_name, response.new_plan 102 | -------------------------------------------------------------------------------- /genworlds/agents/concrete/basic_assistant/thoughts/event_filler.py: -------------------------------------------------------------------------------- 1 | from typing import Type 2 | import json 3 | from genworlds.events.abstracts.event import AbstractEvent 4 | from genworlds.agents.abstracts.agent_state import AbstractAgentState 5 | from genworlds.agents.abstracts.thought import AbstractThought 6 | from langchain.chat_models import ChatOpenAI 7 | from langchain.chains.openai_functions import ( 8 | create_structured_output_chain, 9 | ) 10 | from langchain.chat_models import ChatOpenAI 11 | from langchain.prompts import ChatPromptTemplate 12 | 13 | 14 | class EventFillerThought(AbstractThought): 15 | def __init__( 16 | self, 17 | agent_state: AbstractAgentState, 18 | openai_api_key: str, 19 | model_name: str = "gpt-3.5-turbo", 20 | ): 21 | self.agent_state = agent_state 22 | self.model_name = model_name 23 | self.llm = ChatOpenAI( 24 | model=self.model_name, openai_api_key=openai_api_key, temperature=0.1 25 | ) 26 | 27 | def run(self, trigger_event_class: Type[AbstractEvent]): 28 | prompt = ChatPromptTemplate.from_messages( 29 | [ 30 | ("system", "You are {agent_name}, {agent_description}."), 31 | ( 32 | "system", 33 | "You are embedded in a simulated world with those properties {agent_world_state}", 34 | ), 35 | ("system", "Those are your goals: \n{goals}"), 36 | ( 37 | "system", 38 | "And this is your current plan to achieve the goals: \n{plan}", 39 | ), 40 | ( 41 | "system", 42 | "Here is your memories of all the events that you remember from being in this simulation: \n{memory}", 43 | ), 44 | ( 45 | "system", 46 | "Those are the available entities that you can choose from: \n{available_entities}", 47 | ), 48 | ( 49 | "system", 50 | "Here you have pre-filled parameters coming from your previous thoughts if any: \n{other_thoughts_filled_parameters}", 51 | ), 52 | ( 53 | "system", 54 | "Here is the triggering event schema: \n{triggering_event_schema}", 55 | ), 56 | ("human", "{footer}"), 57 | ] 58 | ) 59 | 60 | chain = create_structured_output_chain( 61 | output_schema=trigger_event_class.schema(), 62 | llm=self.llm, 63 | prompt=prompt, 64 | verbose=True, 65 | ) 66 | response = chain.run( 67 | agent_name=self.agent_state.name, 68 | agent_description=self.agent_state.description, 69 | agent_world_state=self.agent_state.host_world_prompt, 70 | goals=self.agent_state.goals, 71 | plan=self.agent_state.plan, 72 | memory=self.agent_state.last_retrieved_memory, 73 | available_entities=self.agent_state.available_entities, 74 | other_thoughts_filled_parameters=self.agent_state.other_thoughts_filled_parameters, 75 | triggering_event_schema=json.dumps(trigger_event_class.schema()), 76 | footer="""Fill the parameters of the triggering event based on the previous context that you have about the world. 77 | """, 78 | ) 79 | response = trigger_event_class.parse_obj(response) 80 | 81 | return response 82 | -------------------------------------------------------------------------------- /genworlds/agents/concrete/basic_assistant/utils.py: -------------------------------------------------------------------------------- 1 | from typing import List, Optional 2 | from genworlds.agents.abstracts.thought import AbstractThought 3 | from genworlds.events.abstracts.action import AbstractAction 4 | from genworlds.agents.concrete.basic_assistant.agent import BasicAssistant 5 | from genworlds.agents.abstracts.agent_state import AbstractAgentState 6 | 7 | 8 | def generate_basic_assistant( 9 | openai_api_key: str, 10 | agent_name: str, 11 | description: str, 12 | host_world_id: Optional[str] = None, 13 | initial_agent_state: Optional[AbstractAgentState] = None, 14 | other_thoughts: List[AbstractThought] = [], 15 | model_name: str = "gpt-3.5-turbo-1106", 16 | action_classes: List[type[AbstractAction]] = [], 17 | action_schema_chains: List[type[str]] = [], 18 | simulation_memory_persistent_path: str = "./", 19 | ): 20 | """ 21 | Method for generating super simple dummy agents. 22 | """ 23 | 24 | # Static initialized in the constructor 25 | 26 | if not initial_agent_state: 27 | initial_agent_state = AbstractAgentState( 28 | name=agent_name, 29 | id=agent_name, 30 | description=description, 31 | goals=[ 32 | "Starts waiting and sleeps till the user starts a new question.", 33 | f"Once {agent_name} receives a user's question, he makes sure to have all the information before sending the answer to the user.", 34 | f"When {agent_name} has all the required information, he speaks to the user with the results through the agent_speaks_with_user_event.", 35 | "After sending the response, he waits for the next user question.", 36 | "If you have been waiting for any object or entity to send you an event for over 30 seconds, you will wait and sleep until you receive a new event.", 37 | ], 38 | plan=[], 39 | last_retrieved_memory="", 40 | other_thoughts_filled_parameters={}, 41 | available_entities=[], 42 | available_action_schemas={}, 43 | current_action_chain=[], 44 | host_world_prompt="", 45 | simulation_memory_persistent_path=simulation_memory_persistent_path, 46 | memory_ignored_event_types=( 47 | "world_sends_available_action_schemas_event", 48 | "world_sends_available_entities_event", 49 | "agent_wants_updated_state", 50 | ), 51 | wakeup_event_types=set(), # fill 52 | is_asleep=False, 53 | action_schema_chains=action_schema_chains, 54 | ) 55 | 56 | return BasicAssistant( 57 | openai_api_key=openai_api_key, 58 | name=agent_name, 59 | id=agent_name, 60 | description=description, 61 | host_world_id=host_world_id, 62 | initial_agent_state=initial_agent_state, 63 | action_classes=action_classes, 64 | other_thoughts=other_thoughts, 65 | model_name=model_name, 66 | ) 67 | -------------------------------------------------------------------------------- /genworlds/agents/memories/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yeagerai/genworlds/36ab16049a4c215a646ce2a303e1fb90822e193b/genworlds/agents/memories/__init__.py -------------------------------------------------------------------------------- /genworlds/agents/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yeagerai/genworlds/36ab16049a4c215a646ce2a303e1fb90822e193b/genworlds/agents/utils/__init__.py -------------------------------------------------------------------------------- /genworlds/agents/utils/validate_action.py: -------------------------------------------------------------------------------- 1 | import json 2 | from datetime import datetime 3 | from jsonschema import ValidationError, validate 4 | from genworlds.agents.memories.simulation_memory import OneLineEventSummarizer 5 | 6 | 7 | def validate_action( 8 | agent_id: str, 9 | action_schema: str, 10 | pre_filled_event: dict, 11 | available_action_schemas: dict, 12 | ): 13 | one_line_summarizer = OneLineEventSummarizer() # missing key 14 | try: 15 | class_name, event_type = action_schema.split(":") 16 | trigger_event = { 17 | "event_type": event_type, 18 | "sender_id": agent_id, 19 | "created_at": datetime.now().isoformat(), 20 | } 21 | trigger_event.update(pre_filled_event) 22 | summary = one_line_summarizer.summarize(json.dumps(trigger_event)) 23 | trigger_event["summary"] = summary 24 | 25 | event_schema = available_action_schemas[class_name][event_type] 26 | 27 | validate(trigger_event, event_schema) 28 | 29 | if class_name == "Self": 30 | is_my_action = True 31 | else: 32 | is_my_action = False 33 | return is_my_action, trigger_event 34 | except IndexError as e: 35 | return ( 36 | f"Unknown command '{action_schema}'. " 37 | f"Please refer to the 'COMMANDS' list for available " 38 | f"commands and only respond in the specified JSON format." 39 | ) 40 | except ValidationError as e: 41 | return ( 42 | f"Validation Error in args: {str(e)}, pre_filled_event: {pre_filled_event}" 43 | ) 44 | except Exception as e: 45 | return ( 46 | f"Error: {str(e)}, {type(e).__name__}, pre_filled_event: {pre_filled_event}" 47 | ) 48 | -------------------------------------------------------------------------------- /genworlds/events/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yeagerai/genworlds/36ab16049a4c215a646ce2a303e1fb90822e193b/genworlds/events/__init__.py -------------------------------------------------------------------------------- /genworlds/events/abstracts/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yeagerai/genworlds/36ab16049a4c215a646ce2a303e1fb90822e193b/genworlds/events/abstracts/__init__.py -------------------------------------------------------------------------------- /genworlds/events/abstracts/action.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from abc import ABC, abstractmethod 3 | import json 4 | from typing import Any, Type, TypeVar, Generic, Tuple 5 | 6 | from genworlds.events.abstracts.event import AbstractEvent 7 | 8 | T = TypeVar("T", bound=AbstractEvent) 9 | 10 | 11 | class AbstractAction(ABC, Generic[T]): 12 | trigger_event_class: Type[T] 13 | description: str 14 | 15 | def __init__(self, host_object: "AbstractObject"): 16 | self.host_object = host_object 17 | 18 | @property 19 | def action_schema(self) -> Tuple(str): 20 | """Returns the action schema as a string""" 21 | return ( 22 | f"{self.host_object.id}:{self.__class__.__name__}", 23 | f"{self.description}|{self.trigger_event_class.__fields__['event_type'].default}|" 24 | + json.dumps(self.trigger_event_class.schema()), 25 | ) 26 | # f"{type(self.host_object).__name__}|\n{self.host_object.description}|\n" 27 | 28 | @abstractmethod 29 | def __call__(self, event: T, *args: Any, **kwargs: Any) -> Any: 30 | """ 31 | Execute the action, potentially generating one or more events. 32 | Send the events with self.socket_handler.send_event(event). 33 | 34 | :param event: The event that triggers the action. 35 | :param args: Additional positional arguments. 36 | :param kwargs: Additional keyword arguments. 37 | :return: The result of the action execution. The type of the return value can be any type 38 | depending on the implementation. 39 | """ 40 | pass 41 | -------------------------------------------------------------------------------- /genworlds/events/abstracts/event.py: -------------------------------------------------------------------------------- 1 | from abc import ABC 2 | 3 | from typing import Optional 4 | from pydantic import BaseModel, Field 5 | from datetime import datetime 6 | 7 | 8 | class AbstractEvent(ABC, BaseModel): 9 | event_type: str 10 | description: str 11 | summary: Optional[str] 12 | created_at: datetime = Field(default_factory=datetime.now) 13 | sender_id: str 14 | target_id: Optional[str] 15 | -------------------------------------------------------------------------------- /genworlds/objects/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yeagerai/genworlds/36ab16049a4c215a646ce2a303e1fb90822e193b/genworlds/objects/__init__.py -------------------------------------------------------------------------------- /genworlds/objects/abstracts/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yeagerai/genworlds/36ab16049a4c215a646ce2a303e1fb90822e193b/genworlds/objects/abstracts/__init__.py -------------------------------------------------------------------------------- /genworlds/objects/abstracts/object.py: -------------------------------------------------------------------------------- 1 | from typing import List, Type 2 | from genworlds.simulation.sockets.handlers.event_handler import ( 3 | SimulationSocketEventHandler, 4 | ) 5 | from genworlds.events.abstracts.action import AbstractAction 6 | 7 | 8 | class AbstractObject(SimulationSocketEventHandler): 9 | """ 10 | A Class representing a generic object in the simulation. 11 | """ 12 | 13 | def __init__( 14 | self, 15 | name: str, 16 | id: str, 17 | description: str, 18 | host_world_id: str = None, 19 | actions: List[Type[AbstractAction]] = [], 20 | ): 21 | self.host_world_id = host_world_id 22 | self.name = name 23 | self.description = description 24 | 25 | super().__init__(id=id, actions=actions) 26 | -------------------------------------------------------------------------------- /genworlds/simulation/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yeagerai/genworlds/36ab16049a4c215a646ce2a303e1fb90822e193b/genworlds/simulation/__init__.py -------------------------------------------------------------------------------- /genworlds/simulation/simulation.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import threading 4 | from uuid import uuid4 5 | from typing import List 6 | import time 7 | 8 | from genworlds.objects.abstracts.object import AbstractObject 9 | from genworlds.agents.abstracts.agent import AbstractAgent 10 | from genworlds.worlds.abstracts.world import AbstractWorld 11 | 12 | 13 | class Simulation: 14 | def __init__( 15 | self, 16 | name: str, 17 | description: str, 18 | world: AbstractWorld, 19 | objects: List[tuple[AbstractObject, dict]], 20 | agents: List[tuple[AbstractAgent, dict]], 21 | stop_event: threading.Event = None, 22 | ): 23 | self.id = str(uuid4()) 24 | self.name = name 25 | self.description = description 26 | self.world = world 27 | self.objects = objects 28 | self.agents = agents 29 | self.stop_event = stop_event 30 | 31 | def add_agent(self, agent: AbstractAgent, **world_properties): 32 | self.agents.append([agent, world_properties]) 33 | self.agents[-1][0].world_spawned_id = self.world.id 34 | self.world.add_agent(self.agents[-1][0], **self.agents[-1][1]) 35 | self.agents[-1][0].launch() 36 | 37 | def add_object(self, obj: AbstractObject, **world_properties): 38 | self.objects.append([obj, world_properties]) 39 | self.objects[-1][0].world_spawned_id = self.world.id 40 | self.world.add_object(self.objects[-1][0], **self.objects[-1][1]) 41 | self.objects[-1][0].launch_websocket_thread() 42 | 43 | # TODO: delete objects and agents 44 | # TODO: update and restart objects and agents 45 | 46 | def launch(self): 47 | # Register agents and objects with the world 48 | for agent, world_properties in self.agents: 49 | agent.world_spawned_id = self.world.id 50 | self.world.register_agent(agent, **world_properties) 51 | 52 | for obj, world_properties in self.objects: 53 | obj.world_spawned_id = self.world.id 54 | self.world.register_object(obj, **world_properties) 55 | 56 | # Launch the world 57 | self.world.launch_websocket_thread() 58 | 59 | time.sleep(1) 60 | 61 | for agent, world_properties in self.agents: 62 | time.sleep(0.1) 63 | agent.launch() 64 | 65 | for obj, world_properties in self.objects: 66 | time.sleep(0.1) 67 | obj.launch_websocket_thread() 68 | 69 | # Make the application terminate gracefully 70 | while True: 71 | # TODO: pass the stop event to the world and the objects and the agents 72 | if self.stop_event and self.stop_event.is_set(): 73 | break 74 | 75 | try: 76 | time.sleep(1) 77 | except KeyboardInterrupt: 78 | break 79 | -------------------------------------------------------------------------------- /genworlds/simulation/sockets/__init__.py: -------------------------------------------------------------------------------- 1 | from genworlds.simulation.sockets.server import start 2 | -------------------------------------------------------------------------------- /genworlds/simulation/sockets/client.py: -------------------------------------------------------------------------------- 1 | import threading 2 | import json 3 | import time 4 | 5 | import websocket 6 | from colorama import Fore 7 | 8 | from genworlds.utils.logging_factory import LoggingFactory 9 | 10 | 11 | class SimulationSocketClient: 12 | """ 13 | A client for managing connections to a simulation socket server. 14 | """ 15 | 16 | def __init__( 17 | self, 18 | process_event, 19 | url: str = "ws://127.0.0.1:7456/ws", 20 | send_initial_event=None, 21 | reconnect_interval=5, 22 | log_level=None, 23 | ) -> None: 24 | self.url = url 25 | self.websocket = websocket.WebSocketApp( 26 | self.url, 27 | on_open=self.on_open, 28 | on_message=self.on_message, 29 | on_error=self.on_error, 30 | on_close=self.on_close, 31 | ) 32 | # Callback function to process events 33 | self.process_event = process_event 34 | self.send_initial_event = send_initial_event 35 | self.reconnect_interval = reconnect_interval 36 | self.log_level = log_level 37 | 38 | def on_open(self, ws): 39 | self.logger().info(f"Connected to world socket server {self.url}") 40 | if self.send_initial_event: 41 | self.send_initial_event() 42 | self.logger().debug(f"Initial event sent") 43 | 44 | def on_error(self, ws, error): 45 | self.logger().error("World socket client error", exc_info=error) 46 | 47 | def on_close(self, *args): 48 | self.logger().info("World socket client closed connection", args) 49 | if self.reconnect_interval: 50 | self.logger().info( 51 | f"Attempting to reconnect in {self.reconnect_interval} seconds" 52 | ) 53 | time.sleep(self.reconnect_interval) 54 | self.logger().info("Attempting to reconnect") 55 | self.websocket.run_forever() 56 | 57 | def on_message(self, ws, message): 58 | self.logger().debug(f"Received: {message}") 59 | if self.process_event: 60 | self.process_event(json.loads(message)) 61 | 62 | def send_message(self, message): 63 | self.websocket.send(message) 64 | self.logger().debug(f"Sent: {message}") 65 | 66 | def logger(self): 67 | return LoggingFactory.get_logger( 68 | threading.current_thread().name, level=self.log_level 69 | ) 70 | -------------------------------------------------------------------------------- /genworlds/simulation/sockets/handlers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yeagerai/genworlds/36ab16049a4c215a646ce2a303e1fb90822e193b/genworlds/simulation/sockets/handlers/__init__.py -------------------------------------------------------------------------------- /genworlds/simulation/sockets/handlers/event_handler.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from uuid import uuid4 3 | import threading 4 | from typing import List 5 | from genworlds.events.abstracts.action import AbstractAction 6 | from genworlds.simulation.sockets.client import SimulationSocketClient 7 | from genworlds.events.abstracts.event import AbstractEvent 8 | 9 | 10 | class SimulationSocketEventHandler: 11 | def __init__( 12 | self, 13 | id: str, 14 | actions: List[AbstractAction] = [], 15 | external_event_classes: dict[str, AbstractEvent] = {}, 16 | websocket_url: str = "ws://127.0.0.1:7456/ws", 17 | ): 18 | self.event_actions_dict: dict[str, AbstractAction] = {} 19 | self.id = id if id else str(uuid4()) 20 | self.actions = actions 21 | for action in self.actions: 22 | self.register_action(action) 23 | 24 | self.simulation_socket_client = SimulationSocketClient( 25 | process_event=self.process_event, url=websocket_url 26 | ) 27 | 28 | def register_action(self, action: AbstractAction): 29 | event_type = action.trigger_event_class.__fields__["event_type"].default 30 | if event_type not in self.event_actions_dict: 31 | self.event_actions_dict[event_type] = [] 32 | self.event_actions_dict[event_type].append(action) 33 | else: 34 | self.event_actions_dict[event_type].append(action) 35 | 36 | def process_event(self, event): 37 | if event["event_type"] in self.event_actions_dict and ( 38 | event["target_id"] == None or event["target_id"] == self.id 39 | ): 40 | # 0 bc the trigger_event_class is the same for all actions with the same event_type 41 | parsed_event = self.event_actions_dict[event["event_type"]][ 42 | 0 43 | ].trigger_event_class.parse_obj(event) 44 | 45 | for listener in self.event_actions_dict[event["event_type"]]: 46 | listener(parsed_event) 47 | 48 | if "*" in self.event_actions_dict: 49 | for listener in self.event_actions_dict["*"]: 50 | listener(event) 51 | 52 | def send_event(self, event: AbstractEvent): 53 | self.simulation_socket_client.send_message(event.json()) 54 | 55 | def launch_websocket_thread(self): 56 | threading.Thread( 57 | target=self.simulation_socket_client.websocket.run_forever, 58 | name=f"{self.id} Thread", 59 | daemon=True, 60 | ).start() 61 | -------------------------------------------------------------------------------- /genworlds/simulation/sockets/server.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import argparse 4 | import threading 5 | from typing import List 6 | import logging 7 | 8 | from fastapi import FastAPI, WebSocket, WebSocketDisconnect 9 | import uvicorn 10 | 11 | logging.basicConfig(level=logging.INFO) 12 | logger = logging.getLogger(__name__) 13 | 14 | 15 | class WebSocketManager: 16 | def __init__(self): 17 | self.active_connections: List[WebSocket] = [] 18 | 19 | async def connect(self, websocket: WebSocket): 20 | await websocket.accept() 21 | self.active_connections.append(websocket) 22 | 23 | async def disconnect(self, websocket: WebSocket): 24 | self.active_connections.remove(websocket) 25 | 26 | async def send_update(self, data: str): 27 | closed_connections = [] 28 | for connection in self.active_connections: 29 | try: 30 | await connection.send_text(data) 31 | except RuntimeError as e: 32 | if "Unexpected ASGI message" in str(e) and "websocket.close" in str(e): 33 | closed_connections.append(connection) 34 | else: 35 | raise e 36 | for closed_connection in closed_connections: 37 | self.active_connections.remove(closed_connection) 38 | 39 | 40 | app = FastAPI() 41 | websocket_manager = WebSocketManager() 42 | 43 | socket_server_url = None 44 | 45 | 46 | @app.on_event("shutdown") 47 | async def shutdown_event(): 48 | logger.info("SIGTERM received, stopping server...") 49 | sys.exit(0) 50 | 51 | 52 | @app.websocket("/ws") 53 | async def websocket_endpoint(websocket: WebSocket): 54 | await websocket_manager.connect(websocket) 55 | try: 56 | while True: 57 | data = await websocket.receive_text() 58 | logger.debug(f"Received data: {data}") 59 | print(data) 60 | await websocket_manager.send_update(data) 61 | except WebSocketDisconnect as e: 62 | logger.warning(f"WebSocketDisconnect: {e.code}") 63 | except Exception as e: 64 | logger.error(f"Exception: {type(e).__name__}, {e}", exc_info=True) 65 | finally: 66 | await websocket_manager.disconnect(websocket) 67 | 68 | 69 | def start(host: str = "127.0.0.1", port: int = 7456, silent: bool = False, ws_ping_interval: int = 600, ws_ping_timeout: int = 600, timeout_keep_alive: int = 60): 70 | if silent: 71 | sys.stdout = open(os.devnull, "w") 72 | sys.stderr = open(os.devnull, "w") 73 | 74 | uvicorn.run(app, 75 | host=host, 76 | port=port, 77 | log_level="info", 78 | ws_ping_interval=ws_ping_interval, 79 | ws_ping_timeout=ws_ping_timeout, 80 | timeout_keep_alive=timeout_keep_alive) 81 | 82 | if silent: 83 | sys.stdout = sys.__stdout__ 84 | sys.stderr = sys.__stderr__ 85 | 86 | 87 | def start_thread(host: str = "127.0.0.1", port: int = 7456, silent: bool = False): 88 | threading.Thread( 89 | target=start, 90 | name=f"Websocket Server Thread", 91 | daemon=True, 92 | args=( 93 | host, 94 | port, 95 | silent, 96 | ), 97 | ).start() 98 | 99 | 100 | def parse_args(): 101 | parser = argparse.ArgumentParser(description="Start the world socket server.") 102 | parser.add_argument( 103 | "--port", 104 | type=int, 105 | help="The port to start the socket on.", 106 | default=7456, 107 | nargs="?", 108 | ) 109 | parser.add_argument( 110 | "--host", 111 | type=str, 112 | help="The hostname of the socket.", 113 | default="127.0.0.1", 114 | nargs="?", 115 | ) 116 | 117 | return parser.parse_args() 118 | 119 | 120 | def start_from_command_line(): 121 | args = parse_args() 122 | try: 123 | start(host=args.host, port=args.port) 124 | except BaseException as e: 125 | logger.error(e) 126 | sys.exit(0) 127 | 128 | 129 | if __name__ == "__main__": 130 | start_from_command_line() 131 | -------------------------------------------------------------------------------- /genworlds/simulation/sockets/test_client.py: -------------------------------------------------------------------------------- 1 | from time import sleep 2 | import threading 3 | import websocket 4 | 5 | from colorama import Fore 6 | 7 | 8 | class SimulationSocketClient: 9 | def __init__(self) -> None: 10 | self.uri = "ws://127.0.0.1:7456/ws" 11 | self.websocket = websocket.WebSocketApp( 12 | self.uri, 13 | on_open=self.on_open, 14 | on_message=self.on_message, 15 | on_error=self.on_error, 16 | on_close=self.on_close, 17 | ) 18 | print(f"Connected to world socket server {self.uri}") 19 | 20 | def on_open(self, ws): 21 | print(f"World socket client opened connection to {self.uri}") 22 | 23 | def on_message(self, ws, message): 24 | thread_name = threading.current_thread().name 25 | print(f"{Fore.GREEN}[{thread_name}] World socket client received: {message}") 26 | 27 | def on_error(self, ws, error): 28 | print(f"World socket client error: {error}") 29 | 30 | def on_close(self, ws): 31 | print("World socket client closed connection") 32 | 33 | def send_messages_every_10secs(self): 34 | thread_name = threading.current_thread().name 35 | while True: 36 | sleep(10) 37 | message = "Hello World!" 38 | self.websocket.send(message) 39 | print(f"{Fore.CYAN}[{thread_name}] World socket client sent: {message}") 40 | 41 | 42 | def main(): 43 | world_socket_client = SimulationSocketClient() 44 | world_socket_client_2 = SimulationSocketClient() 45 | world_socket_client_3 = SimulationSocketClient() 46 | 47 | threading.Thread( 48 | target=world_socket_client.websocket.run_forever, name="Listener Test Thread" 49 | ).start() 50 | threading.Thread( 51 | target=world_socket_client_2.websocket.run_forever, 52 | name="Listener Test Thread 2", 53 | ).start() 54 | threading.Thread( 55 | target=world_socket_client_3.websocket.run_forever, 56 | name="Listener Test Thread 3", 57 | ).start() 58 | threading.Thread( 59 | target=world_socket_client.send_messages_every_10secs, name="Sender Test Thread" 60 | ).start() 61 | 62 | 63 | main() 64 | -------------------------------------------------------------------------------- /genworlds/simulation/utils/__init__.py: -------------------------------------------------------------------------------- 1 | from genworlds.simulation.helpers.launch_simulation import launch_simulation 2 | -------------------------------------------------------------------------------- /genworlds/simulation/utils/launch_simulation.py: -------------------------------------------------------------------------------- 1 | import threading 2 | from time import sleep 3 | from genworlds.simulation.simulation import Simulation 4 | from genworlds.simulation.sockets.server import start 5 | 6 | 7 | def launch_simulation(simulation: Simulation): 8 | # Start the simulation socket in a parallel thread 9 | simulation_socket = threading.Thread(target=start) 10 | simulation_socket.start() 11 | 12 | # Wait for the socket to start 13 | sleep(1) 14 | 15 | # Launch the simulation 16 | simulation_thread = threading.Thread(target=simulation.launch) 17 | simulation_thread.start() 18 | -------------------------------------------------------------------------------- /genworlds/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yeagerai/genworlds/36ab16049a4c215a646ce2a303e1fb90822e193b/genworlds/utils/__init__.py -------------------------------------------------------------------------------- /genworlds/utils/logging_factory.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | import colorlog 4 | 5 | 6 | class LoggingFactory: 7 | colors = ["red", "yellow", "blue", "purple", "cyan", "green", "white"] 8 | color_index = 0 9 | loggers = {} 10 | 11 | @classmethod 12 | def get_logger(cls, name, level=None): 13 | # If a logger with this name already exists, return it 14 | if name in cls.loggers: 15 | return cls.loggers[name] 16 | 17 | # Create a handler for the logger 18 | handler = colorlog.StreamHandler() 19 | handler.setFormatter( 20 | colorlog.ColoredFormatter( 21 | # f'%(log_color)s%(levelname)-8s%(reset)s %({cls.colors[cls.color_index]})s[%(name)s] %(message)s' 22 | f"%({cls.colors[cls.color_index]})s[%(name)s] %(message)s" 23 | ) 24 | ) 25 | 26 | # Create a logger 27 | logger = colorlog.getLogger(name) 28 | logger.addHandler(handler) 29 | 30 | # Set the logging level 31 | if level == None: 32 | level = os.getenv("LOGGING_LEVEL", logging.INFO) 33 | logger.setLevel(level) 34 | logger.debug(f"Created logger {name} with level {logger.level}") 35 | 36 | # Cache the logger instance 37 | cls.loggers[name] = logger 38 | 39 | # Update color index for next logger 40 | cls.color_index = (cls.color_index + 1) % len(cls.colors) 41 | 42 | return logger 43 | -------------------------------------------------------------------------------- /genworlds/utils/schema_to_model.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from typing import Dict, Any, Optional, Type 3 | from pydantic import create_model, BaseModel 4 | 5 | TYPE_MAPPING = { 6 | "string": (str, ...), 7 | "integer": (int, ...), 8 | "boolean": (bool, ...), 9 | "array": (list, ...), 10 | "object": (dict, ...), 11 | "number": (float, ...), 12 | } 13 | 14 | 15 | def json_schema_to_pydantic_model(schema: Dict[str, Any]) -> Type[BaseModel]: 16 | name = schema.get("title", "DynamicModel") 17 | required_fields = schema.get("required", []) 18 | 19 | fields = {} 20 | for k, v in schema["properties"].items(): 21 | field_type, default_value = TYPE_MAPPING.get(v.get("type"), (Any, ...)) 22 | 23 | # Special handling for date-time format 24 | if v.get("format") == "date-time": 25 | field_type = datetime 26 | 27 | # If the field is not required or has a default value, it's optional 28 | if k not in required_fields and "default" in v: 29 | default_value = v["default"] 30 | field_type = Optional[field_type] 31 | 32 | fields[k] = (field_type, default_value) 33 | 34 | return create_model(name, **fields) 35 | -------------------------------------------------------------------------------- /genworlds/utils/test_user.py: -------------------------------------------------------------------------------- 1 | import threading 2 | from genworlds.simulation.sockets.client import SimulationSocketClient 3 | 4 | 5 | class TestUser: 6 | def __init__( 7 | self, 8 | ): 9 | self.id = "test_user" 10 | self.name = "Test User" 11 | self.description = "A test user for the simulation." 12 | self.socket_client = SimulationSocketClient(process_event=lambda x: "") 13 | threading.Thread( 14 | target=self.socket_client.websocket.run_forever, 15 | name=f"{self.id} Thread", 16 | daemon=True, 17 | ).start() 18 | -------------------------------------------------------------------------------- /genworlds/worlds/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /genworlds/worlds/abstracts/world.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import Generic, TypeVar, List, Type 4 | from time import sleep 5 | from genworlds.objects.abstracts.object import AbstractObject 6 | from genworlds.worlds.abstracts.world_entity import AbstractWorldEntity 7 | 8 | from genworlds.agents.abstracts.agent import AbstractAgent 9 | from genworlds.events.abstracts.action import AbstractAction 10 | from genworlds.simulation.sockets.server import start_thread as socket_server_start 11 | 12 | WorldEntityType = TypeVar("WorldEntityType", bound=AbstractWorldEntity) 13 | 14 | 15 | class AbstractWorld(Generic[WorldEntityType], AbstractObject): 16 | """ 17 | An interface class representing a generic world in the simulation. 18 | """ 19 | 20 | entities: dict[str, AbstractWorldEntity] 21 | action_schemas: dict[str, dict] 22 | 23 | def __init__( 24 | self, 25 | name: str, 26 | id: str, 27 | description: str, 28 | actions: List[Type[AbstractAction]], 29 | objects: List[AbstractObject], 30 | agents: List[AbstractAgent], 31 | get_available_entities: AbstractAction, 32 | get_available_action_schemas: AbstractAction, 33 | ): 34 | self.objects = objects 35 | self.agents = agents 36 | self.get_available_entities = get_available_entities 37 | self.get_available_action_schemas = get_available_action_schemas 38 | super().__init__( 39 | name=name, id=id, description=description, host_world_id=id, actions=actions 40 | ) 41 | 42 | def update_entities(self): 43 | self.entities = {} 44 | self.entities[self.id] = self.get_entity_from_obj(self) 45 | for agent in self.agents: 46 | self.entities[agent.id] = self.get_entity_from_obj(agent) 47 | 48 | for obj in self.objects: 49 | self.entities[obj.id] = self.get_entity_from_obj(obj) 50 | 51 | def update_action_schemas(self): 52 | self.action_schemas = {} 53 | for action in self.actions: 54 | key, value = action.action_schema 55 | self.action_schemas[key] = value 56 | for obj in self.objects: 57 | for action in obj.actions: 58 | key, value = action.action_schema 59 | self.action_schemas[key] = value 60 | for agent in self.agents: 61 | for action in agent.actions: 62 | key, value = action.action_schema 63 | self.action_schemas[key] = value 64 | 65 | def get_entity_from_obj(self, obj: AbstractObject) -> WorldEntityType: 66 | """ 67 | Returns the entity associated with the object. 68 | """ 69 | return AbstractWorldEntity.create(obj) 70 | 71 | def get_entity_by_id(self, entity_id: str) -> AbstractWorldEntity: 72 | return self.entities[entity_id] 73 | 74 | def add_agent(self, agent: AbstractAgent): 75 | self.agents.append(agent) 76 | self.agents[-1].host_world_id = self.id 77 | self.agents[-1].launch() 78 | 79 | def add_object(self, obj: AbstractObject): 80 | self.objects.append(obj) 81 | self.objects[-1].host_world_id = self.id 82 | self.objects[-1].launch_websocket_thread() 83 | 84 | # TODO: delete objects and agents by id (stop thread, then remove from list) 85 | # TODO: update and restart objects and agents close threads and launch new ones 86 | # TODO: be able to stop the world and restart it 87 | 88 | def launch(self, host: str = "127.0.0.1", port: int = 7456): 89 | socket_server_start(host=host, port=port) 90 | sleep(0.2) 91 | self.launch_websocket_thread() 92 | sleep(0.2) 93 | for agent in self.agents: 94 | sleep(0.1) 95 | self.add_agent(agent) 96 | 97 | for obj in self.objects: 98 | sleep(0.1) 99 | self.add_object(obj) 100 | -------------------------------------------------------------------------------- /genworlds/worlds/abstracts/world_entity.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | from typing import Type, Any, Dict 3 | from enum import Enum 4 | from genworlds.agents.abstracts.agent import AbstractAgent 5 | from genworlds.objects.abstracts.object import AbstractObject 6 | from pydantic import BaseModel 7 | 8 | 9 | class EntityTypeEnum(str, Enum): 10 | AGENT = "AGENT" 11 | OBJECT = "OBJECT" 12 | WORLD = "WORLD" 13 | 14 | 15 | def get_entity_type(cls): 16 | from genworlds.worlds.abstracts.world import AbstractWorld 17 | 18 | if issubclass(cls, AbstractAgent): 19 | return EntityTypeEnum.AGENT 20 | elif issubclass(cls, AbstractWorld): 21 | return EntityTypeEnum.WORLD 22 | elif issubclass(cls, AbstractObject): 23 | return EntityTypeEnum.OBJECT 24 | else: 25 | return None 26 | 27 | 28 | class AbstractWorldEntity(BaseModel, ABC): 29 | id: str 30 | entity_type: EntityTypeEnum 31 | entity_class: str 32 | name: str 33 | description: str 34 | 35 | class Config: 36 | extra = "allow" 37 | 38 | @classmethod 39 | def create( 40 | cls: Type["AbstractWorldEntity"], 41 | entity: AbstractObject, 42 | **additional_world_properties: Dict 43 | ) -> "AbstractWorldEntity": 44 | entity_cls = type(entity) 45 | entity_type = get_entity_type(entity_cls) 46 | entity_class = entity_cls.__name__ 47 | id = entity.id 48 | name = entity.name 49 | description = entity.description 50 | # Combine predefined fields with any additional fields provided 51 | entity_data = { 52 | **{ 53 | "id": id, 54 | "entity_type": entity_type, 55 | "entity_class": entity_class, 56 | "name": name, 57 | "description": description, 58 | }, 59 | **additional_world_properties, 60 | } 61 | return cls(**entity_data) 62 | -------------------------------------------------------------------------------- /genworlds/worlds/concrete/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yeagerai/genworlds/36ab16049a4c215a646ce2a303e1fb90822e193b/genworlds/worlds/concrete/__init__.py -------------------------------------------------------------------------------- /genworlds/worlds/concrete/base/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yeagerai/genworlds/36ab16049a4c215a646ce2a303e1fb90822e193b/genworlds/worlds/concrete/base/__init__.py -------------------------------------------------------------------------------- /genworlds/worlds/concrete/base/actions.py: -------------------------------------------------------------------------------- 1 | from genworlds.objects.abstracts.object import AbstractObject 2 | from genworlds.events.abstracts.event import AbstractEvent 3 | from genworlds.events.abstracts.action import AbstractAction 4 | 5 | 6 | class AgentWantsUpdatedStateEvent(AbstractEvent): 7 | event_type = "agent_wants_updated_state" 8 | description = "Agent wants to update its state." 9 | # that gives available_action_schemas, and available_entities 10 | 11 | 12 | class WorldSendsAvailableEntitiesEvent(AbstractEvent): 13 | event_type = "world_sends_available_entities_event" 14 | description = "Send available entities." 15 | available_entities: dict 16 | 17 | 18 | class WorldSendsAvailableEntities(AbstractAction): 19 | trigger_event_class = AgentWantsUpdatedStateEvent 20 | description = "Send available entities." 21 | 22 | def __init__(self, host_object: AbstractObject): 23 | super().__init__(host_object=host_object) 24 | 25 | def __call__(self, event: AgentWantsUpdatedStateEvent): 26 | self.host_object.update_entities() 27 | all_entities = self.host_object.entities 28 | event = WorldSendsAvailableEntitiesEvent( 29 | sender_id=self.host_object.id, 30 | available_entities=all_entities, 31 | target_id=event.sender_id, 32 | ) 33 | self.host_object.send_event(event) 34 | 35 | 36 | class WorldSendsAvailableActionSchemasEvent(AbstractEvent): 37 | event_type = "world_sends_available_action_schemas_event" 38 | description = "The world sends the possible action schemas to all the agents." 39 | world_name: str 40 | world_description: str 41 | available_action_schemas: dict[str, str] 42 | 43 | 44 | class WorldSendsAvailableActionSchemas(AbstractAction): 45 | trigger_event_class = AgentWantsUpdatedStateEvent 46 | description = "The world sends the possible action schemas to all the agents." 47 | 48 | def __init__(self, host_object: AbstractObject): 49 | super().__init__(host_object=host_object) 50 | 51 | def __call__(self, event: AgentWantsUpdatedStateEvent): 52 | self.host_object.update_action_schemas() 53 | self.host_object.update_entities() 54 | all_action_schemas = self.host_object.action_schemas 55 | all_entities = self.host_object.entities 56 | to_delete = [] 57 | for action_schema in all_action_schemas: 58 | if all_entities[action_schema.split(":")[0]].entity_type == "AGENT" and action_schema.split(":")[0] != event.sender_id: 59 | to_delete.append(action_schema) 60 | 61 | if all_entities[action_schema.split(":")[0]].entity_type == "WORLD": 62 | to_delete.append(action_schema) 63 | 64 | if action_schema == f"{event.sender_id}:AgentListensEvents": 65 | to_delete.append(action_schema) 66 | 67 | for action_schema in to_delete: 68 | del all_action_schemas[action_schema] 69 | 70 | event = WorldSendsAvailableActionSchemasEvent( 71 | sender_id=self.host_object.id, 72 | world_name=self.host_object.name, 73 | world_description=self.host_object.description, 74 | available_action_schemas=all_action_schemas, 75 | ) 76 | self.host_object.send_event(event) 77 | 78 | 79 | class UserSpeaksWithAgentEvent(AbstractEvent): 80 | event_type = "user_speaks_with_agent_event" 81 | description = "The user speaks with an agent." 82 | message: str 83 | -------------------------------------------------------------------------------- /genworlds/worlds/concrete/base/world.py: -------------------------------------------------------------------------------- 1 | from uuid import uuid4 2 | from typing import List, Type 3 | from genworlds.events.abstracts.action import AbstractAction 4 | 5 | from genworlds.worlds.abstracts.world import AbstractWorld 6 | from genworlds.worlds.abstracts.world_entity import AbstractWorldEntity 7 | from genworlds.agents.abstracts.agent import AbstractAgent 8 | from genworlds.objects.abstracts.object import AbstractObject 9 | 10 | from genworlds.worlds.concrete.base.actions import ( 11 | WorldSendsAvailableEntities, 12 | WorldSendsAvailableActionSchemas, 13 | ) 14 | 15 | 16 | class BaseWorld(AbstractWorld): 17 | def __init__( 18 | self, 19 | name: str, 20 | description: str, 21 | agents: List[AbstractAgent] = [], 22 | objects: List[AbstractObject] = [], 23 | actions: List[Type[AbstractAction]] = [], 24 | id: str = None, 25 | ): 26 | # availability = all entities 27 | get_available_entities = WorldSendsAvailableEntities(host_object=self) 28 | get_available_action_schemas = WorldSendsAvailableActionSchemas( 29 | host_object=self 30 | ) 31 | actions.append(get_available_entities) 32 | actions.append(get_available_action_schemas) 33 | 34 | super().__init__( 35 | id=id, 36 | name=name, 37 | description=description, 38 | agents=agents, 39 | objects=objects, 40 | actions=actions, 41 | get_available_entities=get_available_entities, 42 | get_available_action_schemas=get_available_action_schemas, 43 | ) 44 | -------------------------------------------------------------------------------- /genworlds/worlds/concrete/community_chat_interface/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yeagerai/genworlds/36ab16049a4c215a646ce2a303e1fb90822e193b/genworlds/worlds/concrete/community_chat_interface/__init__.py -------------------------------------------------------------------------------- /genworlds/worlds/concrete/community_chat_interface/actions.py: -------------------------------------------------------------------------------- 1 | import json 2 | from genworlds.objects.abstracts.object import AbstractObject 3 | from genworlds.events.abstracts.event import AbstractEvent 4 | from genworlds.events.abstracts.action import AbstractAction 5 | 6 | 7 | class UserRequestsScreensToWorldEvent(AbstractEvent): 8 | event_type = "user_requests_screens_to_world" 9 | description = "The user requests the screens to the world." 10 | 11 | 12 | class WorldSendsScreensToUserEvent(AbstractEvent): 13 | event_type = "world_sends_screens_to_user" 14 | description = "The world sends the screens to the user." 15 | screens_config: dict 16 | 17 | 18 | class WorldSendsScreensToUser(AbstractAction): 19 | trigger_event_class = UserRequestsScreensToWorldEvent 20 | description = "The world sends the screens to the user." 21 | 22 | def __init__(self, host_object: AbstractObject): 23 | super().__init__(host_object=host_object) 24 | 25 | def __call__(self, event: UserRequestsScreensToWorldEvent): 26 | with open(self.host_object.screens_config_path) as f: 27 | screens_config = json.load(f) 28 | 29 | event = WorldSendsScreensToUserEvent( 30 | sender_id=self.host_object.id, 31 | screens_config=screens_config, 32 | ) 33 | self.host_object.send_event(event) 34 | -------------------------------------------------------------------------------- /genworlds/worlds/concrete/community_chat_interface/world.py: -------------------------------------------------------------------------------- 1 | from typing import List, Type 2 | 3 | from genworlds.events.abstracts.action import AbstractAction 4 | from genworlds.worlds.abstracts.world import AbstractWorld 5 | from genworlds.agents.abstracts.agent import AbstractAgent 6 | from genworlds.objects.abstracts.object import AbstractObject 7 | from genworlds.worlds.concrete.community_chat_interface.actions import ( 8 | WorldSendsScreensToUser, 9 | ) 10 | from genworlds.worlds.concrete.base.actions import ( 11 | WorldSendsAvailableEntities, 12 | WorldSendsAvailableActionSchemas, 13 | ) 14 | 15 | 16 | class ChatInterfaceWorld(AbstractWorld): 17 | def __init__( 18 | self, 19 | name: str, 20 | description: str, 21 | agents: List[AbstractAgent] = [], 22 | objects: List[AbstractObject] = [], 23 | actions: List[Type[AbstractAction]] = [], 24 | id: str = None, 25 | screens_config_path: str = "./screens_config.json", 26 | ): 27 | self.screens_config_path = screens_config_path 28 | # availability = all entities 29 | get_available_entities = WorldSendsAvailableEntities(host_object=self) 30 | get_available_action_schemas = WorldSendsAvailableActionSchemas( 31 | host_object=self 32 | ) 33 | actions.append(get_available_entities) 34 | actions.append(get_available_action_schemas) 35 | actions.append(WorldSendsScreensToUser(host_object=self)) 36 | 37 | super().__init__( 38 | name=name, 39 | description=description, 40 | agents=agents, 41 | objects=objects, 42 | actions=actions, 43 | get_available_entities=get_available_entities, 44 | get_available_action_schemas=get_available_action_schemas, 45 | id=id, 46 | ) 47 | -------------------------------------------------------------------------------- /genworlds/worlds/concrete/location_based/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yeagerai/genworlds/36ab16049a4c215a646ce2a303e1fb90822e193b/genworlds/worlds/concrete/location_based/__init__.py -------------------------------------------------------------------------------- /genworlds/worlds/concrete/location_based/actions.py: -------------------------------------------------------------------------------- 1 | from genworlds.events.abstracts.event import AbstractEvent 2 | from genworlds.events.abstracts.action import AbstractAction 3 | from genworlds.objects.abstracts.object import AbstractObject 4 | from genworlds.worlds.concrete.base.actions import ( 5 | AgentGetsAvailableEntitiesEvent, 6 | WorldSendsAvailableEntitiesEvent, 7 | WorldSendsAvailableActionSchemasEvent, 8 | ) 9 | 10 | 11 | class AgentMovesToNewLocation(AbstractEvent): 12 | event_type = "agent_moves_to_new_location" 13 | description = "Agent moves to a new location in the world." 14 | destination_location: str 15 | 16 | 17 | class WorldSetsAgentLocationEvent(AbstractEvent): 18 | event_type = "world_sets_agent_location" 19 | description = "The new location has been set for the agent." 20 | 21 | 22 | class WorldSetsAgentLocation(AbstractAction): 23 | trigger_event_class = AgentMovesToNewLocation 24 | 25 | def __init__(self, host_object: AbstractObject): 26 | super().__init__(host_object=host_object) 27 | 28 | def __call__(self, event: AgentMovesToNewLocation): 29 | if event.destination_location not in self.host_object.locations: 30 | raise ValueError( 31 | f"Destination location {event.destination_location} is not in world locations {self.host_object.locations}" 32 | ) 33 | self.host_object.get_entity_by_id( 34 | event.sender_id 35 | ).location = event.destination_location 36 | event = WorldSetsAgentLocationEvent( 37 | sender_id=self.host_object.id, 38 | ) 39 | self.host_object.send_event(event) 40 | 41 | 42 | class WorldSendsSameLocationEntities(AbstractAction): 43 | trigger_event_class = AgentGetsAvailableEntitiesEvent 44 | 45 | def __init__(self, host_object: AbstractObject): 46 | super().__init__(host_object=host_object) 47 | 48 | def __call__(self, event: AgentGetsAvailableEntitiesEvent): 49 | self.host_object.update_entities() 50 | sender_entity = self.host_object.get_entity_by_id(event.sender_id) 51 | same_location_entities = {} 52 | for entity_id, entity in self.host_object.entities.items(): 53 | if entity.location == sender_entity.location: 54 | same_location_entities[entity_id] = entity 55 | event = WorldSendsAvailableEntitiesEvent( 56 | sender_id=self.host_object.id, 57 | target_id=event.sender_id, 58 | available_entities=same_location_entities, 59 | ) 60 | self.host_object.send_event(event) 61 | 62 | 63 | class WorldSendsSameLocationActionSchemas(AbstractAction): 64 | trigger_event_class = AgentGetsAvailableEntitiesEvent ## change that!! 65 | 66 | def __init__(self, host_object: AbstractObject): 67 | super().__init__(host_object=host_object) 68 | 69 | def __call__(self, event: AgentGetsAvailableEntitiesEvent): 70 | self.host_object.update_action_schemas() 71 | sender_entity = self.host_object.get_entity_by_id(event.sender_id) 72 | sender_location = sender_entity.location 73 | same_location_action_schemas = {} 74 | for action_schema_id, action_schema in self.host_object.action_schemas.items(): 75 | entity_id = action_schema_id.split(":")[0] 76 | entity_location = self.host_object.get_entity_by_id(entity_id).location 77 | if entity_location == sender_location: 78 | same_location_action_schemas[action_schema_id] = action_schema 79 | event = WorldSendsAvailableActionSchemasEvent( 80 | sender_id=self.host_object.id, 81 | target_id=event.sender_id, 82 | available_action_schemas=same_location_action_schemas, 83 | ) 84 | self.host_object.send_event(event) 85 | -------------------------------------------------------------------------------- /genworlds/worlds/concrete/location_based/world.py: -------------------------------------------------------------------------------- 1 | from typing import List, Type 2 | from genworlds.worlds.abstracts.world import AbstractWorld 3 | from genworlds.worlds.abstracts.world_entity import AbstractWorldEntity 4 | from genworlds.agents.abstracts.agent import AbstractAgent 5 | from genworlds.objects.abstracts.object import AbstractObject 6 | from genworlds.events.abstracts.action import AbstractAction 7 | 8 | from genworlds.worlds.concrete.location_based.actions import ( 9 | WorldSendsSameLocationEntities, 10 | WorldSendsSameLocationActionSchemas, 11 | WorldSetsAgentLocation, 12 | ) 13 | 14 | 15 | class WorldLocationEntity(AbstractWorldEntity): 16 | location: str = None 17 | 18 | 19 | class LocationWorld(AbstractWorld[WorldLocationEntity]): 20 | locations: list[str] = [] 21 | 22 | def __init__( 23 | self, 24 | name: str, 25 | description: str, 26 | locations: list[str], 27 | agents: List[AbstractAgent] = [], 28 | objects: List[AbstractObject] = [], 29 | actions: List[Type[AbstractAction]] = [], 30 | id: str = None, 31 | ): 32 | self.locations = locations 33 | # availability = same location as sender id 34 | get_available_entities = WorldSendsSameLocationEntities(host_object=self) 35 | get_available_action_schemas = WorldSendsSameLocationActionSchemas( 36 | host_object=self 37 | ) 38 | 39 | actions.append(get_available_entities) 40 | actions.append(get_available_action_schemas) 41 | actions.append(WorldSetsAgentLocation(host_object=self)) 42 | 43 | super().__init__( 44 | name=name, 45 | description=description, 46 | agents=agents, 47 | objects=objects, 48 | actions=actions, 49 | get_available_entities=get_available_entities, 50 | get_available_action_schemas=get_available_action_schemas, 51 | id=id, 52 | ) 53 | 54 | def add_location(self, location: str): 55 | self.locations.append(location) 56 | 57 | def remove_location(self, location: str): 58 | self.locations.remove(location) 59 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "genworlds" 3 | version = "0.0.18" 4 | description = "GenWorlds by YeagerAI: Pioneering AI-based simulations based on collaborative autonomous agents." 5 | authors = [] 6 | 7 | [tool.poetry.dependencies] 8 | python = ">=3.10.0,<3.12" 9 | click="8.1.3" 10 | colorama="0.4.6" 11 | colorlog="6.7.0" 12 | fastapi="0.88.0" 13 | fastjsonschema="2.16.3" 14 | jsonschema="4.17.3" 15 | langchain="0.0.335" 16 | openai="0.28.0" 17 | prompt-toolkit="3.0.38" 18 | pydantic="1.10.7" 19 | python-dotenv="1.0.0" 20 | qdrant-client="1.2.0" 21 | threadpoolctl="3.1.0" 22 | tiktoken="0.4.0" 23 | uvicorn="0.21.1" 24 | websocket-client="1.5.1" 25 | websockets="11.0.3" 26 | 27 | 28 | [tool.poetry.dev-dependencies] 29 | debugpy = "^1.6.2" 30 | black = "^23.3.0" 31 | pytest = "7.4.0" 32 | poetry = "1.5.1" 33 | 34 | [build-system] 35 | requires = ["poetry-core>=1.0.0"] 36 | build-backend = "poetry.core.masonry.api" 37 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | click==8.1.3 2 | colorama==0.4.6 3 | colorlog==6.7.0 4 | fastapi==0.88.0 5 | fastjsonschema==2.16.3 6 | jsonschema==4.17.3 7 | langchain==0.0.215 8 | openai==0.27.4 9 | prompt-toolkit==3.0.38 10 | pydantic==1.10.7 11 | python-dotenv==1.0.0 12 | qdrant-client==1.2.0 13 | threadpoolctl==3.1.0 14 | tiktoken==0.4.0 15 | uvicorn==0.21.1 16 | websocket-client==1.5.1 17 | websockets==11.0.3 -------------------------------------------------------------------------------- /use_cases/foundational_rag/objects/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yeagerai/genworlds/36ab16049a4c215a646ce2a303e1fb90822e193b/use_cases/foundational_rag/objects/__init__.py -------------------------------------------------------------------------------- /use_cases/foundational_rag/objects/local_storage_bucket.py: -------------------------------------------------------------------------------- 1 | import os 2 | from genworlds.objects.abstracts.object import AbstractObject 3 | from genworlds.events.abstracts.action import AbstractAction 4 | from genworlds.events.abstracts.event import AbstractEvent 5 | 6 | import PyPDF2 7 | from docx import Document as DocxDocument 8 | 9 | 10 | # Define the Local Storage Bucket Object 11 | class LocalStorageBucket(AbstractObject): 12 | def __init__(self, id:str, storage_path: str = "./"): 13 | self.storage_path = storage_path 14 | actions = [ConvertFolderToTxt(host_object=self)] 15 | 16 | super().__init__(name="LocalStorage Bucket", 17 | id=id, 18 | description="Object used to consolidate various document types into a single .txt file and store it locally.", 19 | actions=actions 20 | ) 21 | 22 | 23 | # Event for agent's request to convert all files in a folder 24 | class AgentRequestsFolderConversion(AbstractEvent): 25 | event_type = "agent_requests_folder_conversion" 26 | description = "An agent requests conversion of all supported documents in a specific folder to a single txt file." 27 | input_folder_path: str 28 | output_file_path: str 29 | 30 | 31 | # Event sent by LocalStorageBucket to notify agent of completion 32 | class FolderConversionCompleted(AbstractEvent): 33 | event_type = "folder_conversion_completed" 34 | description = "Notifies the agent that the folder has been successfully converted into a txt file." 35 | output_txt_path: str 36 | 37 | class ConvertFolderToTxt(AbstractAction): 38 | trigger_event_class = AgentRequestsFolderConversion 39 | description = "Converts all supported documents in a specific folder to a single txt file." 40 | def __init__(self, host_object: AbstractObject): 41 | super().__init__(host_object=host_object) 42 | 43 | def __call__(self, event: AgentRequestsFolderConversion): 44 | all_texts = [] 45 | input_folder_path = event.input_folder_path 46 | output_file_path = event.output_file_path 47 | 48 | for filename in os.listdir(input_folder_path): 49 | file_path = os.path.join(input_folder_path, filename) 50 | file_extension = os.path.splitext(filename)[1] 51 | 52 | if file_extension == ".md": 53 | with open(file_path, "r", encoding="utf-8") as f: 54 | all_texts.append(f.read()) 55 | 56 | elif file_extension == ".docx": 57 | doc = DocxDocument(file_path) 58 | doc_text = "\n".join([p.text for p in doc.paragraphs]) 59 | all_texts.append(doc_text) 60 | 61 | elif file_extension == ".pdf": 62 | with open(file_path, "rb") as f: 63 | reader = PyPDF2.PdfFileReader(f) 64 | pdf_text = "\n".join( 65 | [ 66 | reader.getPage(i).extractText() 67 | for i in range(reader.numPages) 68 | ] 69 | ) 70 | all_texts.append(pdf_text) 71 | 72 | # Save consolidated text to a new txt file 73 | output_txt_path = os.path.join(self.host_object.storage_path, output_file_path) 74 | with open(output_txt_path, "w", encoding="utf-8") as f: 75 | f.write("\n\n---\n\n".join(all_texts)) 76 | 77 | event = FolderConversionCompleted( 78 | sender_id=self.host_object.id, 79 | target_id=event.sender_id, 80 | output_txt_path=output_txt_path 81 | ) 82 | self.host_object.send_event(event) -------------------------------------------------------------------------------- /use_cases/roundtable/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yeagerai/genworlds/36ab16049a4c215a646ce2a303e1fb90822e193b/use_cases/roundtable/__init__.py -------------------------------------------------------------------------------- /use_cases/roundtable/agents/roundtable_agent.py: -------------------------------------------------------------------------------- 1 | from qdrant_client import QdrantClient 2 | from genworlds.agents.base_agent.base_agent import BaseAgent 3 | from use_cases.roundtable.thoughts.event_filler_thought import EventFillerThought 4 | from use_cases.roundtable.thoughts.navigation_thought import NavigationThought 5 | from use_cases.roundtable.thoughts.podcast_thought import PodcastThought 6 | 7 | 8 | class RoundtableAgent(BaseAgent): 9 | def __init__( 10 | self, 11 | openai_api_key: str, 12 | id: str, 13 | name: str, 14 | role: str, 15 | background: str, 16 | personality: str, 17 | communication_style: str, 18 | topic_of_conversation: str, 19 | goals: list[str], 20 | constraints: list[str], 21 | evaluation_principles: list[str], 22 | personality_db_qdrant_client: QdrantClient = None, 23 | personality_db_collection_name: str = None, 24 | websocket_url: str = "ws://127.0.0.1:7456/ws", 25 | ): 26 | super().__init__( 27 | id=id, 28 | name=name, 29 | description=f"{role}. {background}", 30 | goals=goals, 31 | openai_api_key=openai_api_key, 32 | websocket_url=websocket_url, 33 | personality_db_qdrant_client=personality_db_qdrant_client, 34 | personality_db_collection_name=personality_db_collection_name, 35 | interesting_events={ 36 | "agent_speaks_into_microphone", 37 | "agent_gives_object_to_agent_event", 38 | }, 39 | wakeup_events={ 40 | "agent_gives_object_to_agent_event": { 41 | "recipient_agent_id": id, 42 | } 43 | }, 44 | navigation_brain=NavigationThought( 45 | openai_api_key=openai_api_key, 46 | name=name, 47 | role=role, 48 | background=background, 49 | personality=personality, 50 | topic_of_conversation=topic_of_conversation, 51 | constraints=constraints, 52 | evaluation_principles=evaluation_principles, 53 | n_of_thoughts=3, 54 | ), 55 | execution_brains={ 56 | "podcast_brain": PodcastThought( 57 | openai_api_key=openai_api_key, 58 | name=name, 59 | role=role, 60 | background=background, 61 | personality=personality, 62 | communication_style=communication_style, 63 | topic_of_conversation=topic_of_conversation, 64 | constraints=constraints, 65 | evaluation_principles=evaluation_principles, 66 | n_of_thoughts=1, 67 | ), 68 | "event_filler_brain": EventFillerThought( 69 | openai_api_key=openai_api_key, 70 | name=name, 71 | role=role, 72 | background=background, 73 | topic_of_conversation=topic_of_conversation, 74 | constraints=constraints, 75 | evaluation_principles=evaluation_principles, 76 | n_of_thoughts=1, 77 | ), 78 | }, 79 | action_brain_map={ 80 | "Microphone:agent_speaks_into_microphone": { 81 | "brains": [ 82 | "podcast_brain", 83 | "event_filler_brain", 84 | ], 85 | "next_actions": ["World:agent_gives_object_to_agent_event"], 86 | }, 87 | "World:agent_gives_object_to_agent_event": { 88 | "brains": ["event_filler_brain"], 89 | "next_actions": [], 90 | }, 91 | "default": {"brains": ["event_filler_brain"], "next_actions": []}, 92 | }, 93 | ) 94 | -------------------------------------------------------------------------------- /use_cases/roundtable/events.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | from genworlds.events import BaseEvent 3 | from use_cases.roundtable.objects.job import Job 4 | 5 | 6 | class AgentReadsBlackboardEvent(BaseEvent): 7 | event_type = "agent_reads_blackboard" 8 | description = "An agent reads the blackboard." 9 | 10 | 11 | class BlackboardSendsContentEvent(BaseEvent): 12 | event_type = "blackboard_sends_content" 13 | description = "The blackboard sends its content." 14 | blackboard_content: List[Job] 15 | 16 | 17 | class AgentAddsJobToBlackboardEvent(BaseEvent): 18 | event_type = "agent_adds_job_to_blackboard" 19 | description = "Agent adds a job to the blackboard." 20 | new_job: Job 21 | 22 | 23 | class UserAddsJobToBlackboardEvent(BaseEvent): 24 | event_type = "user_adds_job_to_blackboard" 25 | description = "User adds a job to the blackboard." 26 | new_job: Job 27 | 28 | 29 | class AgentDeletesJobFromBlackboardEvent(BaseEvent): 30 | event_type = "agent_deletes_job_from_blackboard" 31 | description = "An agent deletes a job from the blackboard." 32 | job_id: str 33 | -------------------------------------------------------------------------------- /use_cases/roundtable/migrations/chroma_to_qdrant_migration.py: -------------------------------------------------------------------------------- 1 | # DB migration 2 | def run_chroma_to_qdrant_migration( 3 | collections: list[str], chroma_db_path: str, qdrant_db_path: str 4 | ): 5 | import os 6 | import chromadb 7 | from dotenv import load_dotenv 8 | from langchain.vectorstores import Chroma, Qdrant 9 | from langchain.embeddings import OpenAIEmbeddings 10 | from qdrant_client.http import models as rest 11 | from qdrant_client import QdrantClient 12 | 13 | load_dotenv(dotenv_path=".env") 14 | openai_api_key = os.getenv("OPENAI_API_KEY") 15 | 16 | ABS_PATH = os.path.dirname(os.path.abspath(__file__)) 17 | 18 | embeddings_model = OpenAIEmbeddings(openai_api_key=openai_api_key) 19 | 20 | qdrant_client = QdrantClient(path=qdrant_db_path) 21 | 22 | for collection_name in collections: 23 | print("Migrating collection", collection_name) 24 | client_settings = chromadb.config.Settings( 25 | chroma_db_impl="duckdb+parquet", 26 | persist_directory=chroma_db_path, 27 | anonymized_telemetry=False, 28 | ) 29 | 30 | collection = Chroma( 31 | collection_name=collection_name, 32 | embedding_function=embeddings_model, 33 | client_settings=client_settings, 34 | persist_directory=chroma_db_path, 35 | ) 36 | 37 | items = collection._collection.get( 38 | include=["embeddings", "metadatas", "documents"] 39 | ) 40 | 41 | qdrant_client.recreate_collection( 42 | collection_name=collection_name, 43 | vectors_config=rest.VectorParams( 44 | distance=rest.Distance.COSINE, 45 | size=1536, 46 | ), 47 | ) 48 | 49 | CONTENT_KEY = "page_content" 50 | METADATA_KEY = "metadata" 51 | 52 | qdrant_client.upsert( 53 | collection_name=collection_name, 54 | points=rest.Batch.construct( 55 | ids=items["ids"], 56 | vectors=items["embeddings"], 57 | payloads=Qdrant._build_payloads( 58 | items["documents"], items["metadatas"], CONTENT_KEY, METADATA_KEY 59 | ), 60 | ), 61 | ) 62 | 63 | print("Done") 64 | -------------------------------------------------------------------------------- /use_cases/roundtable/mocked_socket_generator.py: -------------------------------------------------------------------------------- 1 | from collections import deque 2 | import fnmatch 3 | from importlib import import_module 4 | import json 5 | from multiprocessing import Process 6 | import os 7 | import threading 8 | import time 9 | from genworlds.simulation.sockets.simulation_socket_client import SimulationSocketClient 10 | from genworlds.simulation.sockets.server import start_thread 11 | 12 | from world_setup import ( 13 | launch_use_case, 14 | ) # Assuming that launch_use_case is defined in 'another_module.py' 15 | 16 | 17 | def get_use_case_list(): 18 | """ 19 | Get the list of use cases by retrieving the names of folders in the 'use_cases' directory. 20 | :return: JSONResponse with list of use case names 21 | """ 22 | path = "use_cases" 23 | use_cases = [ 24 | dir_name 25 | for dir_name in os.listdir(path) 26 | if os.path.isdir(os.path.join(path, dir_name)) 27 | ] 28 | 29 | world_definitions = [] 30 | for use_case in use_cases: 31 | try: 32 | file_names = os.listdir(os.path.join(path, use_case, "world_definitions")) 33 | except FileNotFoundError: 34 | print( 35 | f"No such directory: {os.path.join(path, use_case, 'world_definitions')}" 36 | ) 37 | continue 38 | 39 | for file_name in file_names: 40 | if fnmatch.fnmatch(file_name, "*.yaml"): 41 | world_definitions.append( 42 | { 43 | "use_case": use_case, 44 | "world_definition": file_name, 45 | } 46 | ) 47 | 48 | return world_definitions 49 | 50 | 51 | def write_dict_to_file(dict_obj, filepath): 52 | with open(filepath, "w") as f: # 'w' for write mode 53 | f.write(json.dumps(dict_obj)) # convert dict to str using json.dumps 54 | f.write("\n") # add a newline at the end for readability 55 | 56 | 57 | def start_server_and_simulation(use_case, world_definition, port): 58 | module_name = f"use_cases.{use_case}.world_setup" 59 | function_name = "launch_use_case" 60 | 61 | module = import_module(module_name) 62 | launch_use_case = getattr(module, function_name) 63 | 64 | # start the server 65 | start_thread(port=port) 66 | 67 | # start the recorder 68 | file_path = os.path.join( 69 | "use_cases", 70 | use_case, 71 | "world_definitions", 72 | world_definition + ".mocked_record.json", 73 | ) 74 | events = [] 75 | 76 | def process_event(event): 77 | events.append(event) 78 | write_dict_to_file({"events": events}, file_path) 79 | 80 | websocket_url = f"ws://127.0.0.1:{port}/ws" 81 | socket_recorder = SimulationSocketClient( 82 | process_event=process_event, 83 | url=websocket_url, 84 | log_level="ERROR", 85 | ) 86 | 87 | threading.Thread( 88 | target=socket_recorder.websocket.run_forever, 89 | name=f"{use_case}/{world_definition} Recorder Thread", 90 | daemon=True, 91 | ).start() 92 | 93 | # start the simulation 94 | launch_use_case( 95 | world_definition=world_definition, 96 | yaml_data_override={ 97 | "world_definition": {"base_args": {"websocket_url": websocket_url}} 98 | }, 99 | ) 100 | 101 | 102 | def kill_process(p): 103 | p.terminate() 104 | p.join() 105 | 106 | 107 | def chunks(lst, n): 108 | """Yield successive n-sized chunks from lst.""" 109 | for i in range(0, len(lst), n): 110 | yield lst[i : i + n] 111 | 112 | 113 | if __name__ == "__main__": 114 | use_case_list = get_use_case_list() 115 | print(use_case_list) 116 | 117 | port = 10000 118 | 119 | running_processes = [] 120 | 121 | parallel_processes = 5 122 | minutes = 30 123 | runtime = minutes * 60 124 | stagger_time = 60 125 | 126 | use_case_chunks = chunks(use_case_list, parallel_processes) 127 | for use_case_list in use_case_chunks: 128 | print(f"Processing {use_case_list}") 129 | for use_case_dict in use_case_list: 130 | use_case = use_case_dict["use_case"] 131 | world_definition = use_case_dict["world_definition"] 132 | 133 | p = Process( 134 | target=start_server_and_simulation, 135 | kwargs={ 136 | "use_case": use_case, 137 | "world_definition": world_definition, 138 | "port": port, 139 | }, 140 | ) 141 | running_processes.append(p) 142 | p.start() 143 | 144 | threading.Timer(runtime, kill_process, args=[p]).start() 145 | 146 | port += 1 147 | # stagger the start of each process 148 | time.sleep(stagger_time) 149 | 150 | time.sleep(runtime + parallel_processes * stagger_time) 151 | 152 | is_any_alive = True 153 | while is_any_alive: 154 | is_any_alive = False 155 | for p in running_processes: 156 | if p.is_alive(): 157 | is_any_alive = True 158 | break 159 | time.sleep(1) 160 | -------------------------------------------------------------------------------- /use_cases/roundtable/objects/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yeagerai/genworlds/36ab16049a4c215a646ce2a303e1fb90822e193b/use_cases/roundtable/objects/__init__.py -------------------------------------------------------------------------------- /use_cases/roundtable/objects/blackboard.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | from genworlds.worlds.base_world import BaseObject 4 | from genworlds.objects.base_object import BaseObject 5 | from use_cases.roundtable.events import ( 6 | AgentReadsBlackboardEvent, 7 | AgentDeletesJobFromBlackboardEvent, 8 | AgentAddsJobToBlackboardEvent, 9 | BlackboardSendsContentEvent, 10 | UserAddsJobToBlackboardEvent, 11 | ) 12 | from use_cases.roundtable.objects.job import Job 13 | 14 | 15 | class Blackboard(BaseObject): 16 | content: List[Job] = [] 17 | 18 | def __init__( 19 | self, 20 | name: str, 21 | description: str, 22 | id: str = None, 23 | websocket_url: str = "ws://127.0.0.1:7456/ws", 24 | ): 25 | super().__init__( 26 | name, 27 | description, 28 | id=id, 29 | websocket_url=websocket_url, 30 | ) 31 | 32 | self.register_event_listeners( 33 | [ 34 | (AgentReadsBlackboardEvent, self.agent_reads_blackboard_listener), 35 | ( 36 | AgentAddsJobToBlackboardEvent, 37 | self.agent_adds_job_to_blackboard_listener, 38 | ), 39 | ( 40 | UserAddsJobToBlackboardEvent, 41 | self.user_adds_job_to_blackboard_listener, 42 | ), 43 | ( 44 | AgentDeletesJobFromBlackboardEvent, 45 | self.agent_deletes_job_from_blackboard_listener, 46 | ), 47 | ] 48 | ) 49 | 50 | def _add_job(self, new_job: Job): 51 | self.content.append(new_job) 52 | 53 | def _delete_job(self, job_id: str): 54 | self.content = [job for job in self.content if job.id != job_id] 55 | 56 | def agent_reads_blackboard_listener(self, event: AgentReadsBlackboardEvent): 57 | print(f"Agent {event.sender_id} reads blackboard {self.id}.") 58 | self.send_event( 59 | BlackboardSendsContentEvent, 60 | target_id=event.sender_id, 61 | blackboard_content=self.content, 62 | ) 63 | 64 | def agent_deletes_job_from_blackboard_listener( 65 | self, event: AgentDeletesJobFromBlackboardEvent 66 | ): 67 | self._delete_job(event.job_id) 68 | 69 | def agent_adds_job_to_blackboard_listener( 70 | self, event: AgentAddsJobToBlackboardEvent 71 | ): 72 | self._add_job(event.new_job) 73 | 74 | def user_adds_job_to_blackboard_listener(self, event: UserAddsJobToBlackboardEvent): 75 | self._add_job(event.new_job) 76 | -------------------------------------------------------------------------------- /use_cases/roundtable/objects/job.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel 2 | 3 | 4 | class Job(BaseModel): 5 | id: str 6 | description: str 7 | assigned_to: str 8 | output_format: str 9 | status: str 10 | # creation_date: 11 | # end_date: 12 | # job_dependencies: 13 | -------------------------------------------------------------------------------- /use_cases/roundtable/objects/microphone.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | from genworlds.events import BaseEvent 3 | 4 | from genworlds.worlds.base_world import BaseObject 5 | from genworlds.objects.base_object import BaseObject 6 | 7 | 8 | class AgentSpeaksIntoMicrophone(BaseEvent): 9 | event_type = "agent_speaks_into_microphone" 10 | description = "The holder of the microphone speaks into the microphone" 11 | message: str 12 | 13 | 14 | class Microphone(BaseObject): 15 | host: str 16 | 17 | def __init__( 18 | self, 19 | name: str, 20 | description: str, 21 | host: str, 22 | id: str = None, 23 | websocket_url: str = "ws://127.0.0.1:7456/ws", 24 | ): 25 | super().__init__( 26 | name, 27 | description, 28 | id=id, 29 | websocket_url=websocket_url, 30 | ) 31 | 32 | self.host = host 33 | 34 | self.register_event_listeners( 35 | [ 36 | (AgentSpeaksIntoMicrophone, self.agent_speaks_into_microphone_listener), 37 | ] 38 | ) 39 | 40 | def agent_speaks_into_microphone_listener(self, event: AgentSpeaksIntoMicrophone): 41 | print(f"Agent {event.sender_id} says: {event.message}.") 42 | -------------------------------------------------------------------------------- /use_cases/roundtable/roundtable_cli_config_example.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "All-in Podcast Test", 3 | "screens": [ 4 | { 5 | "name": "All events", 6 | "has_input": false, 7 | "screen_type": "event_history", 8 | "tracked_events": [ 9 | { 10 | "event_type": "world_sends_schemas_event", 11 | "fields_to_display": [ 12 | {"name":"created_at", "format":"fg:#ff00ff bold"}, 13 | {"name":"event_type", "format":"fg:#ffffff"}, 14 | {"name":"summary","format":"fg:#ffffff"} 15 | ], 16 | "filters":[] 17 | }, 18 | { 19 | "event_type": "agent_speaks_into_microphone", 20 | "fields_to_display": [ 21 | {"name":"created_at", "format":"fg:#ffffff"}, 22 | {"name":"event_type", "format":"fg:#ffffff"}, 23 | {"name":"sender_id", "format":"fg:#ffffff"}, 24 | {"name":"message", "format":"fg:#ffffff"}, 25 | {"name":"summary","format":"fg:#ffffff"} 26 | ], 27 | "filters":[] 28 | }, 29 | { 30 | "event_type": "agent_gives_object_to_agent_event", 31 | "fields_to_display": [ 32 | {"name":"created_at", "format":"fg:#ffffff"}, 33 | {"name":"event_type", "format":"fg:#ffffff"}, 34 | {"name":"sender_id", "format":"fg:#ffffff"}, 35 | {"name":"recipient_agent_id", "format":"fg:#ffffff"}, 36 | {"name":"object_id", "format":"fg:#ffffff"}, 37 | {"name":"summary","format":"fg:#ffffff"} 38 | ], 39 | "filters":[] 40 | }, 41 | { 42 | "event_type": "agent_moves_to_new_location", 43 | "fields_to_display": [ 44 | {"name":"created_at", "format":"fg:#ffffff"}, 45 | {"name":"event_type", "format":"fg:#ffffff"}, 46 | {"name":"sender_id", "format":"fg:#ffffff"}, 47 | {"name":"destination_location", "format":"fg:#ffffff"}, 48 | {"name":"summary","format":"fg:#ffffff"} 49 | ], 50 | "filters":[] 51 | } 52 | ] 53 | }, 54 | { 55 | "name": "Chat Room", 56 | "has_input": false, 57 | "screen_type": "event_history", 58 | "tracked_events": [ 59 | { 60 | "event_type": "agent_speaks_into_microphone", 61 | "fields_to_display": [ 62 | {"name":"created_at", "format":"fg:#ff0000 bold"}, 63 | {"name":"event_type", "format":"fg:#ff0000"}, 64 | {"name":"sender_id", "format":"fg:#ff0000"}, 65 | {"name":"message", "format":"fg:#ff0000"}, 66 | {"name":"summary","format":"fg:#ff0000"} 67 | ], 68 | "filters":[{"field": "sender_id", "value": "jimmy"}] 69 | }, 70 | { 71 | "event_type": "agent_speaks_into_microphone", 72 | "fields_to_display": [ 73 | {"name":"created_at", "format":"fg:#00ff00 bold"}, 74 | {"name":"event_type", "format":"fg:#00ff00"}, 75 | {"name":"sender_id", "format":"fg:#00ff00"}, 76 | {"name":"message", "format":"fg:#00ff00"}, 77 | {"name":"summary","format":"fg:#00ff00"} 78 | ], 79 | "filters":[{"field": "sender_id", "value": "maria"}] 80 | } 81 | ] 82 | } 83 | ], 84 | "settings": {} 85 | } -------------------------------------------------------------------------------- /use_cases/roundtable/thoughts/event_filler_thought.py: -------------------------------------------------------------------------------- 1 | from genworlds.agents.base_agent.thoughts.single_eval_thought_generator import ( 2 | SingleEvalThoughtGenerator, 3 | ) 4 | from genworlds.agents.base_agent.prompts.execution_generator_prompt import ( 5 | ExecutionGeneratorPrompt, 6 | ) 7 | 8 | 9 | class EventFillerThought(SingleEvalThoughtGenerator): 10 | def __init__( 11 | self, 12 | openai_api_key: str, 13 | name: str, 14 | role: str, 15 | background: str, 16 | topic_of_conversation: str, 17 | constraints: list[str], 18 | evaluation_principles: list[str], 19 | n_of_thoughts: int, 20 | ): 21 | event_filler_output_parameter_generator = lambda llm_params: llm_params[ 22 | "command_to_execute" 23 | ]["args"] 24 | 25 | super().__init__( 26 | openai_api_key=openai_api_key, 27 | prompt_template_class=ExecutionGeneratorPrompt, 28 | llm_params=[ 29 | "plan", 30 | "goals", 31 | "memory", 32 | "personality_db", 33 | "agent_world_state", 34 | "nearby_entities", 35 | "inventory", 36 | "command_to_execute", 37 | "previous_brain_outputs", 38 | ], 39 | output_parameter_generator=event_filler_output_parameter_generator, 40 | n_of_thoughts=n_of_thoughts, 41 | generator_role_prompt=f"""You are {name}, {role}. In previous steps, you have selected an action to execute, and possibly generated some of the response parameters - make sure to include those exactly. 42 | You now need to generate a valid set of JSON parameters for the command to execute, based on the following information: 43 | 44 | ## Your background 45 | {background} 46 | 47 | ## The topic of conversation 48 | {topic_of_conversation} 49 | """, 50 | generator_results_prompt=f""" 51 | # Response type 52 | Generate exactly {{num_thoughts}} possible options for completing the arguments of the command to execute 53 | 54 | ## Constraints 55 | {constraints} 56 | """, 57 | evaluator_role_prompt=f"You are {name}, {role}. ou need to evaluate the provided set of JSON parameters based on their correctness, with respect to all of the following information:", 58 | evaluator_results_prompt=f""" 59 | ## Parameters to evaluate 60 | Evaluate the following list of possible parameter sets in terms of their correctness: 61 | {{thought_to_evaluate}} 62 | 63 | ## Evaluation principles 64 | {evaluation_principles} 65 | 66 | ## Constraints 67 | - target_id must be the ID of the entity that provides the action 68 | {constraints} 69 | 70 | # Response type 71 | Return the best parameter set 72 | """, 73 | verbose=True, 74 | model_name="gpt-4", 75 | ) 76 | -------------------------------------------------------------------------------- /use_cases/roundtable/thoughts/navigation_thought.py: -------------------------------------------------------------------------------- 1 | from genworlds.agents.base_agent.thoughts.single_eval_thought_generator import ( 2 | SingleEvalThoughtGenerator, 3 | ) 4 | from genworlds.agents.base_agent.prompts.navigation_generator_prompt import ( 5 | NavigationGeneratorPrompt, 6 | ) 7 | 8 | 9 | class NavigationThought(SingleEvalThoughtGenerator): 10 | def __init__( 11 | self, 12 | openai_api_key: str, 13 | name: str, 14 | role: str, 15 | background: str, 16 | personality: str, 17 | topic_of_conversation: str, 18 | constraints: list[str], 19 | evaluation_principles: list[str], 20 | n_of_thoughts: int, 21 | ): 22 | navigation_output_parameter_generator = lambda llm_params: { 23 | "inventory": { 24 | "type": "array", 25 | "description": "list the ids of the items in your inventory", 26 | "items": { 27 | "type": "string", 28 | }, 29 | }, 30 | "step_completed": { 31 | "type": "string", 32 | "description": "If you completed a step of your plan, put it here, as well as an explanation of how you did it", 33 | }, 34 | "plan": { 35 | "type": "array", 36 | "description": "A numbered list of an updated plan", 37 | "items": { 38 | "type": "string", 39 | }, 40 | }, 41 | "next_action_aim": { 42 | "type": "string", 43 | "description": "What is the aim of the next action you want to take?", 44 | }, 45 | "next_action": { 46 | "type": "string", 47 | "enum": list(llm_params["relevant_commands"].keys()), 48 | "description": 'What is the next action you want to take? Example: "EntityClass:action_type_1"', 49 | }, 50 | "violated_constraints": { 51 | "type": "array", 52 | "description": """ 53 | List any constraints form the section "## Constraints" that this action would violate. 54 | """, 55 | "items": { 56 | "type": "string", 57 | }, 58 | }, 59 | "is_action_valid": { 60 | "type": "string", 61 | "description": """ 62 | Assess whether the selected next action is valid based on the current state of the world. 63 | Example: "Yes, opening the door is valid because I have the key to the door in my inventory" 64 | """, 65 | }, 66 | } 67 | 68 | super().__init__( 69 | openai_api_key=openai_api_key, 70 | prompt_template_class=NavigationGeneratorPrompt, 71 | llm_params=[ 72 | "plan", 73 | "goals", 74 | "memory", 75 | # "personality_db", 76 | "agent_world_state", 77 | "nearby_entities", 78 | "inventory", 79 | "relevant_commands", 80 | "messages", 81 | ], 82 | output_parameter_generator=navigation_output_parameter_generator, 83 | n_of_thoughts=n_of_thoughts, 84 | generator_role_prompt=f""" 85 | You are {name}, {role}. You need to come up with the next step to get you closer to your goals. It must be consistent with all of the following information: 86 | 87 | ## Your background 88 | {background} 89 | 90 | ## Your personality 91 | {personality} 92 | 93 | ## Topic of conversation 94 | {topic_of_conversation} 95 | """, 96 | generator_results_prompt=f""" 97 | ## Constraints 98 | {constraints} 99 | 100 | # Response type 101 | Look at your previous plan, your memories about what you have done recently and your goals, and propose {{num_thoughts}} possible plans that advance your goals. 102 | Check if you have already completed a step in your plan, and if so, propose the next step as the first one. 103 | Then, select the next action that you want to do to achieve the first step of your plan. 104 | Check that the action is in the list of available actions, and that you meet all the preconditions for the action. 105 | If you do not have a plan, propose a new plan. 106 | """, 107 | evaluator_role_prompt=f"You are {name}, {role}. You are trying to evaluate a number of actions to see which will get you closer to your goals. It must be consistent with the following information:", 108 | evaluator_results_prompt=f""" 109 | ## Choose the best action 110 | In a previous step, you have generated some possible plans. Now, you need to evaluate them and choose the best one. 111 | 112 | ## Evaluation principles 113 | {evaluation_principles} 114 | 115 | ## Constraints 116 | - Check that the proposed action is in the list of available actions 117 | - That you meet all the preconditions for the action, like having the correct items in your inventory 118 | {constraints} 119 | 120 | # Response type 121 | Return the best plan of the following options: 122 | {{thought_to_evaluate}} 123 | """, 124 | verbose=True, 125 | model_name="gpt-4", 126 | ) 127 | -------------------------------------------------------------------------------- /use_cases/roundtable/thoughts/podcast_thought.py: -------------------------------------------------------------------------------- 1 | from genworlds.agents.base_agent.thoughts.single_eval_thought_generator import ( 2 | SingleEvalThoughtGenerator, 3 | ) 4 | from genworlds.agents.base_agent.prompts.execution_generator_prompt import ( 5 | ExecutionGeneratorPrompt, 6 | ) 7 | 8 | 9 | class PodcastThought(SingleEvalThoughtGenerator): 10 | def __init__( 11 | self, 12 | openai_api_key: str, 13 | name: str, 14 | role: str, 15 | background: str, 16 | personality: str, 17 | communication_style: str, 18 | topic_of_conversation: str, 19 | constraints: list[str], 20 | evaluation_principles: list[str], 21 | n_of_thoughts: int, 22 | ): 23 | navigation_output_parameters_generator = lambda _: { 24 | "podcast_response": { 25 | "type": "string", 26 | "description": "Your proposed response to the podcast", 27 | }, 28 | } 29 | 30 | super().__init__( 31 | openai_api_key=openai_api_key, 32 | prompt_template_class=ExecutionGeneratorPrompt, 33 | llm_params=[ 34 | # "plan", 35 | "goals", 36 | "memory", 37 | "personality_db", 38 | "agent_world_state", 39 | "nearby_entities", 40 | # "inventory", 41 | "command_to_execute", 42 | "previous_brain_outputs", 43 | ], 44 | output_parameter_generator=navigation_output_parameters_generator, 45 | n_of_thoughts=n_of_thoughts, 46 | generator_role_prompt=f""" 47 | You are {name}, {role}. You have to generate a podcast response based on the following information: 48 | 49 | ## Your background 50 | {background} 51 | 52 | ## Your personality 53 | {personality} 54 | 55 | ## Your communication style 56 | {communication_style} 57 | 58 | ## The topic of conversation 59 | {topic_of_conversation} 60 | """, 61 | generator_results_prompt=f""" 62 | # Response type 63 | Output exactly {{num_thoughts}} different possible paragraphs of text that would be a good next line for your to say in line with the goal you set for yourself. 64 | Do not narrate any actions you might take, only generate a piece of text. 65 | Try to make the tone of the text consistent with your personality and communication style. 66 | Relate what you say to what has been said recently. 67 | 68 | ## Constraints 69 | {constraints} 70 | """, 71 | evaluator_role_prompt=f"You are {name}, {role}. You are trying to evaluate a number possible things to next on the podcast. It must be consistent with the following information:", 72 | evaluator_results_prompt=f""" 73 | ## Evaluation principles 74 | {evaluation_principles} 75 | 76 | ## Constraints 77 | - Check that the proposed action is in the list of available actions 78 | - That you meet all the preconditions for the action, like having the correct items in your inventory 79 | {constraints} 80 | 81 | ## Outputs to evaluate 82 | Evaluate the following paragraphs of text and choose the best one to say next. 83 | {{thought_to_evaluate}} 84 | 85 | # Response type 86 | Output the best response. 87 | """, 88 | verbose=True, 89 | model_name="gpt-4", 90 | ) 91 | -------------------------------------------------------------------------------- /use_cases/simple_collaboration_method/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yeagerai/genworlds/36ab16049a4c215a646ce2a303e1fb90822e193b/use_cases/simple_collaboration_method/__init__.py --------------------------------------------------------------------------------