diff --git a/.fdignore b/.fdignore new file mode 100644 index 00000000..b3e89b70 --- /dev/null +++ b/.fdignore @@ -0,0 +1,5 @@ +.* +LICENSE +*.{bin,pickle,parquet,png,pkl} +vectordb +docs \ No newline at end of file diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 00000000..57f9f818 --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1,6 @@ +!.gitignore +!* +!*/* +cache_db.json +cache_tree.json +vector_cache diff --git a/docs/cache_title.json b/docs/cache_title.json new file mode 100644 index 00000000..b976b477 --- /dev/null +++ b/docs/cache_title.json @@ -0,0 +1 @@ +{"_default": {"1": {"path": "/FAQ.md", "hash": "5fd883ee99cd77038e25e9814b2a6fab", "title": "Voyager FAQs: Solutions and Costs"}, "2": {"path": "/FAQ.md:1-12", "hash": "80cbfbe9442fe36ec0f3a517a8756dd6", "title": "Voyager FAQ: Troubleshooting and Usage"}, "3": {"path": "/FAQ.md:12-18", "hash": "0930074bb90899bc9173cdd2ff51cb15", "title": "Fix 'KeyError: 'access_token'' by Changing Redirect URI Type"}, "4": {"path": "/FAQ.md:18-38", "hash": "34ea583f57cf5e045e22be82da23cc8b", "title": "Troubleshooting Mineflayer Installation"}, "5": {"path": "/FAQ.md:40-45", "hash": "9a7f738120d065e4b664a389b125aa3f", "title": "Resetting Environment & Bot View in Mineflayer"}, "6": {"path": "/FAQ.md:48-54", "hash": "7e41aa001f699b520bd68e9ea826c988", "title": "Upgrading from GPT-3.5 to GPT-4"}, "7": {"path": "/README.md", "hash": "e7ac7ee0d3bfdb25273f78aa01432854", "title": "AI Mastermind Tackles Minecraft, Excels"}, "8": {"path": "/README.md:1-24", "hash": "06065432d4ebfef0c9bb17ebd4c71175", "title": "Embodied Agent for Minecraft Exploration"}, "9": {"path": "/README.md:25-38", "hash": "ca4ee7eac7dca85c46f7b52dbc3728f5", "title": "Voyager: SOTA Skill Library with GPT-4 Integration and Lifelong Learning"}, "10": {"path": "/README.md:40-72", "hash": "07160c44e65378e1b9355b8dab7f0015", "title": "Install Voyager: Python 3.9, Node.js 16.13.0, Minecraft"}, "11": {"path": "/README.md:74-100", "hash": "8e70c1ceb43a67e50aeb5f52fdfdc404", "title": "Setup and Play Voyager"}, "12": {"path": "/README.md:101-130", "hash": "7b4f6b6423113702412f31b17b291577", "title": "Setting Up Voyager Minecraft World"}, "13": {"path": "/README.md:131-157", "hash": "8167a821b1833d6e0d72bb5143ccfd43", "title": "Voyager Agent Decomposes Tasks for Inference"}, "14": {"path": "/README.md:158-164", "hash": "2f5ca9dd512619d152f8d6a7f102aa40", "title": "Research Paper Metadata"}, "15": {"path": "/installation/fabric_mods_install.md", "hash": "cfc257ac240215407d179b68a53cab6c", "title": "Fabric Mods Installation Guide"}, "16": {"path": "/installation/fabric_mods_install.md:1-6", "hash": "d8a3095023a63a13663fb4143c381393", "title": "Fabric Mods: Installation Guide"}, "17": {"path": "/installation/fabric_mods_install.md:7-13", "hash": "4a0e37b1cce5fadb782b13abfd7749e8", "title": "Fabric Mods Installation Guide"}, "18": {"path": "/installation/fabric_mods_install.md:13-22", "hash": "67aba67c858c413582a4c3fc0ad5ba10", "title": "Fabric Mod Installation: Azure Login Setup"}, "19": {"path": "/installation/minecraft_instance_install.md", "hash": "9b57bc77ea7405d1a4f77afc7f99f483", "title": "Setup Azure Login for Minecraft Voyager"}, "20": {"path": "/installation/minecraft_instance_install.md:1-12", "hash": "694559d4e8ec8e193ecdadaf25d54607", "title": "Voyager Minecraft on Azure Setup"}, "21": {"path": "/installation/minecraft_instance_install.md:13-22", "hash": "35ac24cbde5abce473fbcc12e72fd8dd", "title": "Collect Azure Login Info"}, "22": {"path": "/installation/minecraft_instance_install.md:23-41", "hash": "40ba3afe5ded01b7c2f59f3fb05a709d", "title": "Azure Login and Minecraft Instance Setup"}, "23": {"path": "/requirements.txt", "hash": "190ffc74c60a881b41ce40aa66ccf99f", "title": "Essential Libraries for Project Functionality"}, "24": {"path": "/setup.py", "hash": "bdbcc786d29f943d886de18a661ac4f8", "title": "Setting up a Python Package"}, "25": {"path": "/setup.py:1-42", "hash": "c9ac4172eeeafabc345e432020f6f9ba", "title": "Setting Up Voyager Python Package"}, "26": {"path": "/setup.py:43-59", "hash": "f52b39268d80627e9b382091183a3a04", "title": "Python Package Setup with Setup.py"}, "27": {"path": "/skill_library/README.md", "hash": "6bc5cde95849dae9aa0e3a778b9774e9", "title": "Skill Library: Community Contributions and Sharing"}, "28": {"path": "/skill_library/README.md:1-34", "hash": "25a46e2ee2106e0a67d56e5f2f104d8c", "title": "Skill Library Contributions"}, "29": {"path": "/skill_library/README.md:35-44", "hash": "54c8de8f7bd6bd17dd5f916062c90324", "title": "Shared Skill Library"}, "30": {"path": "/voyager/__init__.py", "hash": "26a01fc1631b22c779d72137c636fb88", "title": "Importing Voyager Class"}, "31": {"path": "/voyager/agents/__init__.py", "hash": "08765c6a7fe2ba624c731f4ac79570c9", "title": "Importing Voyager's Agents"}, "32": {"path": "/voyager/agents/action.py", "hash": "dccf182b615d71df8728112eabe95a5b", "title": "ActionAgent: Streamlined ChatOpenAI-Powered Gaming Agent"}, "33": {"path": "/voyager/agents/action.py:1-34", "hash": "52b78ede1df43246cb4e0afc582545bc", "title": "ActionAgent Class Initialization and Features"}, "34": {"path": "/voyager/agents/action.py:35-59", "hash": "8cef7bd60a8acadee8c7e92c8522aac8", "title": "Chest Memory Agent Class"}, "35": {"path": "/voyager/agents/action.py:60-86", "hash": "488c88cafa3a429ead2c77a14fcb7a0b", "title": "Chest Information Fetcher"}, "36": {"path": "/voyager/agents/action.py:87-112", "hash": "a1587d5d19b281eeeddcec520476c516", "title": "System Message Initialization and Rendering"}, "37": {"path": "/voyager/agents/action.py:113-135", "hash": "3b749823c29076c1c02526ada7c7920a", "title": "Event Handler and Observation String Construction"}, "38": {"path": "/voyager/agents/action.py:137-166", "hash": "2a13717f812b58db9475a27892d665da", "title": "Agent Observation Generation Code Snippet"}, "39": {"path": "/voyager/agents/action.py:168-199", "hash": "dcad7bf209830349bf42272b944d0ea6", "title": "Agent Observation String Generation"}, "40": {"path": "/voyager/agents/action.py:201-226", "hash": "03a796fdbbcb5150e6ca094428fe2bfb", "title": "Parsing AI Messages with JavaScript Support"}, "41": {"path": "/voyager/agents/action.py:227-246", "hash": "1eaf36a271012b2b5a77cebac2374e12", "title": "Last Async Function Executor"}, "42": {"path": "/voyager/agents/action.py:247-270", "hash": "a05ea7f545d96e70ad19843f98ba43f4", "title": "Action Response Parser and Chat Log Filter"}, "43": {"path": "/voyager/agents/action.py:271-280", "hash": "38d6524d592d49521fb2b4179d760176", "title": "Event Filter and Chatlog Updater"}, "44": {"path": "/voyager/agents/critic.py", "hash": "8f6bcf07d94acdd52ecabc8230fcbaa1", "title": "Voyager CriticAgent: Manual or Auto Mode, Task Evaluation"}, "45": {"path": "/voyager/agents/critic.py:1-32", "hash": "02337adb518db5da93ee276e97fe3bdf", "title": "Critic Agent OpenAI Chat Class"}, "46": {"path": "/voyager/agents/critic.py:33-60", "hash": "3b9004de5f9a78628ef0bec2d006e03e", "title": "Observation Construction"}, "47": {"path": "/voyager/agents/critic.py:62-91", "hash": "fdc2dd8c534396aa22afc9201bb08a5a", "title": "AI-Assisted Task Evaluation and User Critique"}, "48": {"path": "/voyager/agents/critic.py:92-119", "hash": "402571ab45b5c1e3b81bbe4a302878f5", "title": "Critic Agent JSON Parsing and Retry"}, "49": {"path": "/voyager/agents/critic.py:120-138", "hash": "73585af31e4b8af7f6bbdbb9240d825e", "title": "Critic Agent: Initialize and Check Task Success"}, "50": {"path": "/voyager/agents/curriculum.py", "hash": "47f10b0966e699f0b2ee55629a803edd", "title": "Curriculum Agent: Task-Based Learning with LangChain"}, "51": {"path": "/voyager/agents/curriculum.py:1-37", "hash": "1dbf2d07974926871c396d060e814e19", "title": "Curriculum Agent Initializes LangChain LLMs"}, "52": {"path": "/voyager/agents/curriculum.py:38-63", "hash": "03b03b33fca85d12b1730dffef083bf7", "title": "Initializing Curriculum Agent"}, "53": {"path": "/voyager/agents/curriculum.py:64-82", "hash": "b34bc49e35ab471c965941995b5e623c", "title": "Initializing Curriculum Agent's QA Cache"}, "54": {"path": "/voyager/agents/curriculum.py:83-118", "hash": "37c111bc4db4eea0a2f2934f5abe7e3a", "title": "Default Warm-Up Values for Curriculum Agent"}, "55": {"path": "/voyager/agents/curriculum.py:119-149", "hash": "004a3143b113687a4ef39ab115ec4d81", "title": "Voyager Agent Class Definition"}, "56": {"path": "/voyager/agents/curriculum.py:150-183", "hash": "e21b9bdc428521bb1070c86815435d00", "title": "Biome Detector Algorithm"}, "57": {"path": "/voyager/agents/curriculum.py:184-202", "hash": "c3d4dd9f787cfbe603dab89d7f0c1a2c", "title": "Agent Progress and Inventory Observation"}, "58": {"path": "/voyager/agents/curriculum.py:203-228", "hash": "22eb7c3ccd280c5b5cc9b9207411057e", "title": "Contextual Question Generator"}, "59": {"path": "/voyager/agents/curriculum.py:229-251", "hash": "e410d07be9045c16e7611715c857cde1", "title": "Curriculum Agent: Task Proposal and Mining"}, "60": {"path": "/voyager/agents/curriculum.py:252-264", "hash": "397ae9b4849d0ebe8048f369fd0b0e59", "title": "Chest Inventory Management"}, "61": {"path": "/voyager/agents/curriculum.py:265-290", "hash": "675b5c918817141b8ebd150374a39e5c", "title": "Craft or Place Chest Task"}, "62": {"path": "/voyager/agents/curriculum.py:292-315", "hash": "acb12c4f7d690b3d97da03519a416586", "title": "AI Task Retrieval and Parsing"}, "63": {"path": "/voyager/agents/curriculum.py:316-343", "hash": "5830ad56e7bc908de2ae37750e1472a6", "title": "Curriculum Agent Functions"}, "64": {"path": "/voyager/agents/curriculum.py:344-372", "hash": "320fd3e07614bd8c9ff738ee2ef6bb57", "title": "Curriculum Task Cleaner and Decomposer"}, "65": {"path": "/voyager/agents/curriculum.py:373-397", "hash": "2255e172cc6d3965ec1af90de5d233ed", "title": "Curriculum Agent Task Decomposer"}, "66": {"path": "/voyager/agents/curriculum.py:398-418", "hash": "c91455cbb96f0a0c04b12d2756921d3c", "title": "Cache and Generate QA Pairs in Curriculum Agent"}, "67": {"path": "/voyager/agents/curriculum.py:419-441", "hash": "fa2c246f65d6c0fee5b62a690f08fd87", "title": "AI Game Agent: Question-Answer Cache"}, "68": {"path": "/voyager/agents/curriculum.py:442-465", "hash": "09c60b819dbdb95459e92e2dd0d605c9", "title": "Methods for Generating Messages and Asking Questions in Minecraft"}, "69": {"path": "/voyager/agents/curriculum.py:466-490", "hash": "d5a628381647da833126557eba9dcffd", "title": "Question Pair Extraction and Verification"}, "70": {"path": "/voyager/agents/curriculum.py:491-498", "hash": "72df15ad38f9f566e51810f3f0df9966", "title": "QA Agent Message Rendering"}, "71": {"path": "/voyager/agents/skill.py", "hash": "5b00b97ade843fdaa2b6703935496b63", "title": "Skill Manager Initialization"}, "72": {"path": "/voyager/agents/skill.py:1-34", "hash": "06b9fdca740bd345940459dcc3e603d7", "title": "Skill Manager Initialization"}, "73": {"path": "/voyager/agents/skill.py:35-57", "hash": "6f4ccfda3b729ae440ff03eae8c28d21", "title": "Skill Manager Initialization"}, "74": {"path": "/voyager/agents/skill.py:58-80", "hash": "aa792985d8bdaa211c79b431b5ace42b", "title": "Generate and Add Skills in Database"}, "75": {"path": "/voyager/agents/skill.py:81-108", "hash": "b4f5bf2c51df9042c05a3da6ff29dbd4", "title": "Skill Management Code"}, "76": {"path": "/voyager/agents/skill.py:109-127", "hash": "6e39d8cfe753d4f55fc326d52ea8b6eb", "title": "Skill Search with Vector Database"}, "77": {"path": "/voyager/control_primitives/__init__.py", "hash": "09bd6739b3ef11b3eb0fc8b033eda0b9", "title": "Load Voyager Control Primitives"}, "78": {"path": "/voyager/control_primitives/craftHelper.js", "hash": "1f051033fa2bed7738a37d75a319b46c", "title": "Crafting Helper"}, "79": {"path": "/voyager/control_primitives/craftHelper.js:1-26", "hash": "bf56de31ea204fc816bad0c00b20c384", "title": "Crafting Table Recipe Selector"}, "80": {"path": "/voyager/control_primitives/craftHelper.js:27-54", "hash": "ad031fe664187c34d79178cb9983ef00", "title": "Check Recipe for Missing Items"}, "81": {"path": "/voyager/control_primitives/craftHelper.js:55-61", "hash": "86b6ec165030caa4b65385237e92acf3", "title": "Craft Helper Checks Items"}, "82": {"path": "/voyager/control_primitives/craftItem.js", "hash": "ee97c762f31cdbc21d28d916964e7e73", "title": "Asynchronous Crafting Validation with Feedback"}, "83": {"path": "/voyager/control_primitives/craftItem.js:1-30", "hash": "f67d6a54dd169b3bce60e4acd052022d", "title": "Asynchronous Crafting Function"}, "84": {"path": "/voyager/control_primitives/craftItem.js:31-43", "hash": "7679eefd1ee50ba79b46b39f57f93988", "title": "Error-catching Recipe Craft Limit"}, "85": {"path": "/voyager/control_primitives/exploreUntil.js", "hash": "d42e0081b4febbd5a6fbbb56534cada1", "title": "Explore Timeout Function"}, "86": {"path": "/voyager/control_primitives/exploreUntil.js:1-33", "hash": "3653a41af5851665e61b72127ac02255", "title": "Explore Until Function Guide"}, "87": {"path": "/voyager/control_primitives/exploreUntil.js:34-67", "hash": "3866c7d2e78a262774c5a247ff28c901", "title": "Interval-based Exploration Function"}, "88": {"path": "/voyager/control_primitives/exploreUntil.js:68-87", "hash": "c80ed847ef3740b3d851ca3cfb605ddb", "title": "Explore Until Timeout"}, "89": {"path": "/voyager/control_primitives/givePlacedItemBack.js", "hash": "2d189f39bba26b2fd59330454ef21832", "title": "Disable, Enable Tile Drops and Give Back Items"}, "90": {"path": "/voyager/control_primitives/givePlacedItemBack.js:1-24", "hash": "20565a9b5d346ce0cec4642536ed8505", "title": "Re-enable Tile Drops for Bots"}, "91": {"path": "/voyager/control_primitives/givePlacedItemBack.js:25-38", "hash": "a9343b1413de3af83c25091138c64562", "title": "Air and Destroy Block"}, "92": {"path": "/voyager/control_primitives/killMob.js", "hash": "b55ce6d31494b8aff7020edf361beb44", "title": "Kill Mob Progress Saving"}, "93": {"path": "/voyager/control_primitives/killMob.js:1-32", "hash": "171a7a49a67cead7caf7df6a72da1c0f", "title": "Check Mob Presence and Distance"}, "94": {"path": "/voyager/control_primitives/killMob.js:33-51", "hash": "e6c49327e9509ca175b080c2acb8b322", "title": "Kill and Collect: Mob Script"}, "95": {"path": "/voyager/control_primitives/mineBlock.js", "hash": "1f082af7d015cc8f2043a402ffb4c506", "title": "MineBlock: Collecting and Verifying Bot Operations"}, "96": {"path": "/voyager/control_primitives/mineBlock.js:1-32", "hash": "9aab91445c1ecdb23aa38cb4a3cec367", "title": "Mine Block Function"}, "97": {"path": "/voyager/control_primitives/mineBlock.js:33-37", "hash": "e5c306bef4a328c19effc3cbf395fa51", "title": "Mine Block State Save"}, "98": {"path": "/voyager/control_primitives/placeItem.js", "hash": "4686a07f2be3463d94ae545c31ed4b1d", "title": "Verifying and Placing Inventory Items"}, "99": {"path": "/voyager/control_primitives/placeItem.js:1-32", "hash": "32c3ec372691101ad59122acfbddb0a5", "title": "Place Item Function: Checking and Placement"}, "100": {"path": "/voyager/control_primitives/placeItem.js:33-62", "hash": "d21bddfe862540a8bdd2980c2351f7a9", "title": "Place Item Control Primitives"}, "101": {"path": "/voyager/control_primitives/placeItem.js:63-79", "hash": "7e06e7ca127ed0ce28316ded47192969", "title": "Verify and Save Inventory Items"}, "102": {"path": "/voyager/control_primitives/shoot.js", "hash": "4aa962f9c80eab8ce7f587cb385bde98", "title": "Auto-Attack Bot Function"}, "103": {"path": "/voyager/control_primitives/smeltItem.js", "hash": "96a4e7d6a1bf9a7943894baa9cdf51c4", "title": "Smelt Item Function for Bots"}, "104": {"path": "/voyager/control_primitives/smeltItem.js:1-29", "hash": "97efda0b1a969f1b69514d4eb21b41fb", "title": "Smelt Item with Furnace"}, "105": {"path": "/voyager/control_primitives/smeltItem.js:30-56", "hash": "bb29e01071f43a29d8cc55e0a752b254", "title": "Smelting Bot Items in Furnace"}, "106": {"path": "/voyager/control_primitives/smeltItem.js:57-68", "hash": "dd19868a6e6cb121e23e2373c21b73f4", "title": "Smelt Failure Handling"}, "107": {"path": "/voyager/control_primitives/useChest.js", "hash": "08391249557406d6b52a58d4d6ca586e", "title": "Bot-Controlled Chest Interaction"}, "108": {"path": "/voyager/control_primitives/useChest.js:1-31", "hash": "fef010293dff220710a8cb8c89db489f", "title": "Chest Item Retrieval Function"}, "109": {"path": "/voyager/control_primitives/useChest.js:32-62", "hash": "3209a1edcb574be71b2222895535cda7", "title": "Chest Deposit Function"}, "110": {"path": "/voyager/control_primitives/useChest.js:63-94", "hash": "5c7e3bb6a6ef75bb792108fb9f9506d5", "title": "Chest Positioning and Deposit Code Snippet"}, "111": {"path": "/voyager/control_primitives/useChest.js:95-133", "hash": "257e0c886bd4be9ef9b649d422e92648", "title": "Interacting with Chests in Games"}, "112": {"path": "/voyager/control_primitives/waitForMobRemoved.js", "hash": "a752dfcf78cd94f7adae006bb4899cc0", "title": "Wait for Mob Removal"}, "113": {"path": "/voyager/control_primitives/waitForMobRemoved.js:1-31", "hash": "5cf318f46455d04087126841f2bd9f45", "title": "Wait for Mob Removal Detection"}, "114": {"path": "/voyager/control_primitives/waitForMobRemoved.js:32-65", "hash": "e73946030748db8293fc8ac344f168cd", "title": "Wait for Mob Removal & Item Drop"}, "115": {"path": "/voyager/control_primitives/waitForMobRemoved.js:66-84", "hash": "b40209a65a37efab1534bc9c20d8e30c", "title": "Detect Mob Removal with Event Listeners"}, "116": {"path": "/voyager/control_primitives_context/__init__.py", "hash": "bc6953d64a5ae6b200fad358da48b32b", "title": "Voyager Control Primitives Context"}, "117": {"path": "/voyager/control_primitives_context/craftItem.js", "hash": "fbf1dcface1c16e0b3ed574841cb740f", "title": "Crafting Recipe: Follow and Create"}, "118": {"path": "/voyager/control_primitives_context/exploreUntil.js", "hash": "7697f82d694cef5c6574ccf9ca9d1b78", "title": "Exploring Iron Ore and Pigs with Voyager's exploreUntil"}, "119": {"path": "/voyager/control_primitives_context/exploreUntil.js:1-29", "hash": "527fa46f6b104c3ec0b6b5add8b4ff6b", "title": "Explore Until: Iron Ore and Pig Detector"}, "120": {"path": "/voyager/control_primitives_context/exploreUntil.js:30-31", "hash": "33e96b93e14700bc4d25fe9ea5cc703e", "title": "Explore Until: Voyager's Control Primitives"}, "121": {"path": "/voyager/control_primitives_context/killMob.js", "hash": "f57144e50afee9838b5d47bd426c3fc1", "title": "Mob Killing Function: killMob"}, "122": {"path": "/voyager/control_primitives_context/mineBlock.js", "hash": "9cca1c2a114ae09c9e4315c15e64581f", "title": "Mine Block Efficiently"}, "123": {"path": "/voyager/control_primitives_context/mineflayer.js", "hash": "c1f3bbe9b8b54a1e7bfc9ec201806358", "title": "Mastering Mineflayer Bot Controls"}, "124": {"path": "/voyager/control_primitives_context/mineflayer.js:1-10", "hash": "61da7164c8aba02677a8a101836de0b6", "title": "Mineflayer Goals and Functions"}, "125": {"path": "/voyager/control_primitives_context/mineflayer.js:11-20", "hash": "a2b2114baf5b701951811a59e6c1107e", "title": "Mineflayer: Minecraft Bot Functions"}, "126": {"path": "/voyager/control_primitives_context/mineflayer.js:20-22", "hash": "1b0518d86b8d607c68c7e8b47d1afcaa", "title": "Activating and Using Items in Mineflayer.js"}, "127": {"path": "/voyager/control_primitives_context/placeItem.js", "hash": "58a8da2243ea5a7044181ed3066f5387", "title": "Item Placement Algorithm"}, "128": {"path": "/voyager/control_primitives_context/placeItem.js:1-25", "hash": "9d4b370dccb557f3836a42f0cfc576e2", "title": "Place Item Near Player"}, "129": {"path": "/voyager/control_primitives_context/placeItem.js:26-28", "hash": "d4cf25f2a58f407206d723a129c9c513", "title": "Equip and Place Block in Bot's Hand"}, "130": {"path": "/voyager/control_primitives_context/smeltItem.js", "hash": "7a2ff4d7690c90f974661a9434a4740a", "title": "Furnace Smelting Function"}, "131": {"path": "/voyager/control_primitives_context/useChest.js", "hash": "288c5c5656ccbbd06c623fe2e5b42a7a", "title": "Chest Deposit Functions"}, "132": {"path": "/voyager/control_primitives_context/useChest.js:1-19", "hash": "4f4cdb854e840d1e6964341c346c438b", "title": "Interact with Chest at (30, 65, 100)"}, "133": {"path": "/voyager/control_primitives_context/useChest.js:20-35", "hash": "39e82bd12935e7b16d9c5cd20355766b", "title": "Chest Interaction Functions"}, "134": {"path": "/voyager/env/__init__.py", "hash": "7d7b606e2981500132df36206b91fe8f", "title": "Voyager Env Package"}, "135": {"path": "/voyager/env/bridge.py", "hash": "0e2bd204e39c25791f46d3e5af1353b6", "title": "Azure Minecraft Server Control with VoyagerEnv"}, "136": {"path": "/voyager/env/bridge.py:1-38", "hash": "866770c7200f584f3492a24fa39faff8", "title": "VoyagerEnv: Minecraft Environment Class"}, "137": {"path": "/voyager/env/bridge.py:39-68", "hash": "d5624110ef16386ca33242565f3f349a", "title": "Minecraft Server Setup with Mineflayer"}, "138": {"path": "/voyager/env/bridge.py:69-93", "hash": "391e74aa9e88cd9bbdbeb63f5de7a431", "title": "Minecraft Server Monitor & Restarter"}, "139": {"path": "/voyager/env/bridge.py:94-125", "hash": "b6555fc935f9636989beacadecb2f025", "title": "Minecraft Server Control Class"}, "140": {"path": "/voyager/env/bridge.py:127-158", "hash": "8a856aab7aa9f85f0726687162c85ff6", "title": "Reset and Render Methods"}, "141": {"path": "/voyager/env/bridge.py:159-189", "hash": "48e0de64aee741b3967b5eb26c06066c", "title": "Stop, Pause, Unpause Minecraft Server API"}, "142": {"path": "/voyager/env/minecraft_launcher.py", "hash": "3635dc33483de04c328c1914ef0b1928", "title": "Minecraft Launcher Script"}, "143": {"path": "/voyager/env/minecraft_launcher.py:1-41", "hash": "d69fe2a68a63ad9f9f14bf4549c9c46f", "title": "Minecraft Launcher Class Definition"}, "144": {"path": "/voyager/env/minecraft_launcher.py:42-69", "hash": "c69be69ab1dd2a149b2f3e47fb54ce2a", "title": "Initialize Mineflayer Process"}, "145": {"path": "/voyager/env/minecraft_launcher.py:70-100", "hash": "b5c28c6e95be381d86ba76b1664cff35", "title": "Minecraft Launcher Login Process"}, "146": {"path": "/voyager/env/minecraft_launcher.py:101-127", "hash": "63d363222b2c85fe9d471f0f16d21f8c", "title": "Minecraft Server Launcher Class"}, "147": {"path": "/voyager/env/process_monitor.py", "hash": "97bc9780a3d6a05bcb083795224e1aa7", "title": "Subprocess Lifecycle Manager"}, "148": {"path": "/voyager/env/process_monitor.py:1-38", "hash": "f910838ad427e50277f1c0eaadeba623", "title": "Process Monitoring Class"}, "149": {"path": "/voyager/env/process_monitor.py:39-65", "hash": "4863b0ed17b3024b73db3886b62f04fa", "title": "Subprocess Monitor and Callback Setup"}, "150": {"path": "/voyager/env/process_monitor.py:66-90", "hash": "b9f011e112ced16f53a99fa189ecded8", "title": "Process Manager Class with Lifecycle Control"}, "151": {"path": "/voyager/prompts/__init__.py", "hash": "0397f93df951147c1004370671f39176", "title": "Loading Prompts via Voyager"}, "152": {"path": "/voyager/prompts/action_response_format.txt", "hash": "053ccc091f0be332054de48cd6ca271a", "title": "Asynchronous Main Function: \"yourMainFunctionName\""}, "153": {"path": "/voyager/prompts/action_template.txt", "hash": "a9ff22d14ed74825c8595ca7dc7ff388", "title": "Reusable Mineflayer"}, "154": {"path": "/voyager/prompts/action_template.txt:1-32", "hash": "a94e01403a7199051fe8c8f8c66cb78f", "title": "Automate Minecraft with Mineflayer"}, "155": {"path": "/voyager/prompts/action_template.txt:33-39", "hash": "7e70fb7fc3a5147627a0370bbe1cbbae", "title": "Efficient Botless Building"}, "156": {"path": "/voyager/prompts/action_template.txt:40-49", "hash": "577ab23ddccb0d11a9bc9399674c34fb", "title": "Chat Bot Code Guidelines"}, "157": {"path": "/voyager/prompts/critic.txt", "hash": "8e6da3237417a1b1f29d2f0cf0ad6c44", "title": "Minecraft Task Evaluation Critic"}, "158": {"path": "/voyager/prompts/critic.txt:1-16", "hash": "a54e696b415492eeb08fcaf458dbdea5", "title": "Minecraft Task Progress Assessment"}, "159": {"path": "/voyager/prompts/critic.txt:17-59", "hash": "6162f2edbb73d24dd4b44af1f383880f", "title": "JSON Response Generator and Critiquer"}, "160": {"path": "/voyager/prompts/critic.txt:60-113", "hash": "e71350a06be8e7819072e3f6f4a39c98", "title": "Zombie Survival Game Task Evaluator"}, "161": {"path": "/voyager/prompts/critic.txt:113-127", "hash": "44affb5484d4f0362a1fc8874427c0a1", "title": "Inventory Optimizer"}, "162": {"path": "/voyager/prompts/curriculum.txt", "hash": "0ecaf4dfa78a0f3a851eb29680214d9a", "title": "Minecraft Task Suggestions"}, "163": {"path": "/voyager/prompts/curriculum.txt:1-26", "hash": "5d6db2b619ea05e4182a03249378b2a3", "title": "Minecraft Assistant: Personalized Learning Path"}, "164": {"path": "/voyager/prompts/curriculum.txt:27-32", "hash": "555cb792a89b949ae475d52cb8c17447", "title": "Task Formatting Guidelines"}, "165": {"path": "/voyager/prompts/curriculum.txt:33-42", "hash": "85c2efdb2566b733be6cfc2c932ad4f4", "title": "Task Avoidance Guidelines and Examples"}, "166": {"path": "/voyager/prompts/curriculum_qa_step1_ask_questions.txt", "hash": "3319faa460d1542bbdcd8ed3c44f8b30", "title": "Minecraft Biome Prompts Generator"}, "167": {"path": "/voyager/prompts/curriculum_qa_step1_ask_questions.txt:1-24", "hash": "4c81cb32f3cf300ae33c09f8368be2fe", "title": "Ask Minecraft Questions AI"}, "168": {"path": "/voyager/prompts/curriculum_qa_step1_ask_questions.txt:25-46", "hash": "1fb89c6b95c0279a2ccb38daa3c1409b", "title": "Effective Prompts for Curriculum"}, "169": {"path": "/voyager/prompts/curriculum_qa_step1_ask_questions.txt:47-68", "hash": "8eb008d28f6675b98aa306a3ca288495", "title": "Game Prompt Questions"}, "170": {"path": "/voyager/prompts/curriculum_qa_step1_ask_questions.txt:68-94", "hash": "1bd99a986f9ee221c5b526db31805b7f", "title": "Ask Specific Questions"}, "171": {"path": "/voyager/prompts/curriculum_qa_step2_answer_questions.txt", "hash": "874bb9fbecab37849b51caba976d173a", "title": "Minecraft Q&A Helper"}, "172": {"path": "/voyager/prompts/curriculum_task_decomposition.txt", "hash": "dd619b0d1725f7dd49be0ce8ed2573ea", "title": "Minecraft Task Decomposition AI"}, "173": {"path": "/voyager/prompts/skill.txt", "hash": "536306dada56372866808e4dc6d04dd2", "title": "Mine Cobblestone with Wooden Pickaxe"}, "174": {"path": "/voyager/prompts/skill.txt:1-24", "hash": "0df5c510f3de31b7dbcd07a3954d65ac", "title": "Craft and Collect Cobblestones"}, "175": {"path": "/voyager/prompts/skill.txt:25-51", "hash": "e81cfb34ade274c9b3ff0e46d99538a0", "title": "Wooden Pickaxe Mine Event"}, "176": {"path": "/voyager/utils/__init__.py", "hash": "31e9af7000a57a38ba573aac6d67cb7e", "title": "Voyager Utilities"}, "177": {"path": "/voyager/utils/file_utils.py", "hash": "39083ceecebafdb3dd2229482a7ff5cd", "title": "File Utilities: Comprehensive Python File Operations"}, "178": {"path": "/voyager/utils/file_utils.py:1-65", "hash": "50d6da29e095520a4503768898de0b02", "title": "File Utility Functions"}, "179": {"path": "/voyager/utils/file_utils.py:66-107", "hash": "f4984e4f8a2bf40688eb34f89e0f741f", "title": "File Utilities: Expand, Join, Exist"}, "180": {"path": "/voyager/utils/file_utils.py:108-136", "hash": "7527bbbcb791d44ce063c9f1aaf89b03", "title": "Directory Scanner: Recursive, Filtered File Listing"}, "181": {"path": "/voyager/utils/file_utils.py:137-177", "hash": "16ceea907bdc473405c966620fb3a976", "title": "File Utilities: List, Create Directories"}, "182": {"path": "/voyager/utils/file_utils.py:178-227", "hash": "6e843b5ed69801ef6ac01988fe080b11", "title": "File Utility Functions"}, "183": {"path": "/voyager/utils/file_utils.py:228-262", "hash": "d316cdcf1ebc23e16d63190cb772ec56", "title": "File and Directory Utilities"}, "184": {"path": "/voyager/utils/file_utils.py:263-299", "hash": "bd12f96472640988645716451d1ea08e", "title": "Copy Tree Function for Files and Folders"}, "185": {"path": "/voyager/utils/file_utils.py:300-319", "hash": "ef52e55e5614d80e7493561dacc99342", "title": "Symlink-Aware File Copying: Preserving Permissions and Handling Dangling Symlinks"}, "186": {"path": "/voyager/utils/file_utils.py:320-350", "hash": "69e610b060a65b3a2cbdd3802c860ec7", "title": "Ignore Patterns for CopyTree"}, "187": {"path": "/voyager/utils/file_utils.py:351-387", "hash": "9f756433437117c9cbbe50847b0373b3", "title": "File Path Manipulation Utilities"}, "188": {"path": "/voyager/utils/file_utils.py:388-431", "hash": "febd69939cd45f625f75e36112e8d9cf", "title": "File Utilities: Path and Directory Manipulation"}, "189": {"path": "/voyager/utils/file_utils.py:434-463", "hash": "0ea3037fdbe37a8ffe47e94e1279f254", "title": "MD5 Checksum & Tarball Creation Utility"}, "190": {"path": "/voyager/utils/file_utils.py:465-496", "hash": "350415ff86944e5005e43e8c264f57bd", "title": "Voyager File Utilities: Archive, Extract, and Move with Backup"}, "191": {"path": "/voyager/utils/file_utils.py:499-538", "hash": "02e6abbce4d6e00f7c94547838141fd7", "title": "File Utilities and Locking"}, "192": {"path": "/voyager/utils/file_utils.py:539-568", "hash": "3f30ba2351b402d55e66c482eb853d0b", "title": "Load and Dump Data Functions"}, "193": {"path": "/voyager/utils/json_utils.py", "hash": "58d8b7afd37b1f8cf3f40c09700faa33", "title": "JSON Validator with Aliases and Corrections"}, "194": {"path": "/voyager/utils/json_utils.py:1-44", "hash": "8afae93ca228fcb7e1de4d302343a462", "title": "JSON Functions and Error Handling"}, "195": {"path": "/voyager/utils/json_utils.py:45-83", "hash": "198067e11c841a36da173c090151194b", "title": "JSON Syntax Utilities"}, "196": {"path": "/voyager/utils/json_utils.py:84-120", "hash": "51d4c20b2be07f4a8c847bca627c53fd", "title": "Fix Invalid JSON Errors"}, "197": {"path": "/voyager/utils/json_utils.py:121-152", "hash": "29053c263b4e8fd911202cfba104420d", "title": "Fixing and Parsing Invalid JSON Strings"}, "198": {"path": "/voyager/utils/json_utils.py:153-176", "hash": "78db2fafc1879d1909f39e3443f6cb9c", "title": "Handling JSON Strings with Preceding Text"}, "199": {"path": "/voyager/utils/json_utils.py:177-200", "hash": "2f5e3021b0cb2478da6fd48d83e5dfd9", "title": "AI-Driven JSON Repair and Loading"}, "200": {"path": "/voyager/utils/json_utils.py:201-226", "hash": "5533710811299367d135d8d5f32de31a", "title": "Fix Invalid JSON Format with Triple Backticks"}, "201": {"path": "/voyager/utils/json_utils.py:227-227", "hash": "d178ecb91bee73eaddb0318624afb0ce", "title": "Conditional 'Failed' Return"}, "202": {"path": "/voyager/utils/record_utils.py", "hash": "c01cf307f6b1d074b692d8de87851e50", "title": "EventRecorder: Record, Update, Sort"}, "203": {"path": "/voyager/utils/record_utils.py:1-38", "hash": "23e10bd23f5d4b585f24c33a0572b5a5", "title": "Event Recorder Initialization and Management"}, "204": {"path": "/voyager/utils/record_utils.py:39-64", "hash": "024ab629e39ac8979be7271cdac4c108", "title": "Recorder: Timestamped Event Recording Class"}, "205": {"path": "/voyager/utils/record_utils.py:65-88", "hash": "e803336222d6d00907ed9ed70f30ea93", "title": "Event Data Tracking and History"}, "206": {"path": "/voyager/utils/record_utils.py:89-102", "hash": "48e63c3c913be08030d26b948ccb74a9", "title": "Recording Position and Elapsed Time Updates"}, "207": {"path": "/voyager/voyager.py", "hash": "a871d477a542546020fd1fb3fe9cd5a8", "title": "Voyager: Comprehensive AI Communication Tool"}, "208": {"path": "/voyager/voyager.py:1-35", "hash": "1a0f1d5bc06f2f70fcc840adc58a2d6c", "title": "Voyager: AI Agent's Main Module"}, "209": {"path": "/voyager/voyager.py:36-57", "hash": "dbe7fd7654e1d2811e94e76350ed6d2e", "title": "Voyager Class: Iterative Prompting, Curriculum Agent"}, "210": {"path": "/voyager/voyager.py:58-72", "hash": "945c0914b7143a3fbc1608762a42c5ed", "title": "Minecraft Bot Configuration Parameters"}, "211": {"path": "/voyager/voyager.py:73-94", "hash": "09c05db2509c913e5b8fc295b859d88a", "title": "Curriculum and Agent Parameters"}, "212": {"path": "/voyager/voyager.py:95-120", "hash": "a32b9a3ef7d027a78c87fd35dda43900", "title": "VoyagerEnv and ActionAgent Initiation"}, "213": {"path": "/voyager/voyager.py:121-143", "hash": "15e7b9503e67dfe00b9d862b16164538", "title": "Agent Instantiation in Voyager AI System"}, "214": {"path": "/voyager/voyager.py:144-172", "hash": "f62cead0ae2e495a7e96cbaff17bba0c", "title": "Voyager Agent Initialization and Resetting"}, "215": {"path": "/voyager/voyager.py:173-196", "hash": "fbd746abd7cd46aa59a7ed9f1c178009", "title": "Setting Up Agent Environment and Messages"}, "216": {"path": "/voyager/voyager.py:197-221", "hash": "0415ff48ebb82354cd5a3ab663ea4072", "title": "Agent Actions and Conversations Initiation"}, "217": {"path": "/voyager/voyager.py:222-243", "hash": "1cd6e26844d9c3eaa0e4bb89bba88007", "title": "Reverting Failed Events in Voyager.py"}, "218": {"path": "/voyager/voyager.py:244-267", "hash": "007e9dfe48d39fea14beec1516ab82e4", "title": "Skill Updates and Retry Handling"}, "219": {"path": "/voyager/voyager.py:268-296", "hash": "643782ce17e95e58772cd116969a9ce8", "title": "Rollout and Learn in Voyager Class"}, "220": {"path": "/voyager/voyager.py:297-326", "hash": "74c313eccf9f83adf62e98fa108303b8", "title": "Code: Environment Reset and Inventory Management"}, "221": {"path": "/voyager/voyager.py:327-350", "hash": "1b7d75af4d0bba75d100fb41a39b77cf", "title": "Error Handling Rollout Task"}, "222": {"path": "/voyager/voyager.py:351-380", "hash": "11d1d51b73a86c7f1fdd36db9823c731", "title": "Voyager Python Functions"}, "223": {"path": "/voyager/voyager.py:381-405", "hash": "47dc80adf4a6df457c9f932b80226724", "title": "Reset and Iterate Sub-Goals Rollout"}, "224": {"path": "/voyager/voyager.py:406-411", "hash": "61068515a9b1352568b151d3299d3fd0", "title": "Colorful Task Summary Printing"}}} \ No newline at end of file diff --git a/docs/codeview.html b/docs/codeview.html new file mode 100755 index 00000000..761065f4 --- /dev/null +++ b/docs/codeview.html @@ -0,0 +1,669 @@ + + + + + + + + + Code View + + + + + + + + + + + + + + + + + + + + +
+

Code Preview

+
+
+
+ +
+
+ + + \ No newline at end of file diff --git a/docs/data/0.json b/docs/data/0.json new file mode 100644 index 00000000..5289ae8a --- /dev/null +++ b/docs/data/0.json @@ -0,0 +1,541 @@ +{ + "0": { + "file_id": 0, + "content": "/FAQ.md", + "type": "filepath" + }, + "1": { + "file_id": 0, + "content": "This code tackles Voyager program FAQs and Mineflayer issues, covering solutions for errors, bots' behavior, installation, and limitations. It discusses using GPT-4 over GPT-3.5 with prompt length configuration, and estimates costs at around 50 USD for 160 iterations, advising to monitor expenses and bot behavior.", + "type": "summary" + }, + "2": { + "file_id": 0, + "content": "# Frequently Asked Questions\n* [I got connection error after I click on the Azure login link and login to Microsoft account.](#i-got-a-connection-error-after-i-click-on-the-azure-login-link-and-login-to-my-microsoft-account)\n* [I got `KeyError: 'access_token'` after I copied the link](#i-got-keyerror-accesstoken-after-i-copied-the-link)\n* [I got `Subprocess Mineflayer failed to start` error.](#i-got-subprocess-mineflayer-failed-to-start-error)\n* [I saw the bot left and rejoin the game after each task.](#i-saw-the-bot-left-and-rejoin-the-game-after-each-task)\n* [How to show the bot's first-person perspective?](#how-to-show-the-bots-first-person-view)\n* [Can I use GPT-3.5 instead of GPT-4?](#can-i-use-gpt-35-instead-of-gpt-4)\n* [What's the estimated cost of running Voyager?](#whats-the-estimated-cost-of-running-voyager)\n## I got a connection error after I click on the Azure login link and login to my Microsoft account.\nIt's normal that you get a connection refused or 404 error after you log in. Y", + "type": "code", + "location": "/FAQ.md:1-12" + }, + "3": { + "file_id": 0, + "content": "Code snippet discusses frequently asked questions related to the Voyager program, including connection errors, access token issues, bot behavior, and usage options.", + "type": "comment" + }, + "4": { + "file_id": 0, + "content": "ou will still see the new URL in your browser. You just need to copy and paste that link. It should contain things like `code=M.C....` in that link.\n## I got `KeyError: 'access_token'` after I copied the link\nWhile testing Voyager, we use Redirect URI Type: `Public client/native (mobile & desktop)` in the app registration for Azure Login. However, according to the report in issue [#34](https://github.com/MineDojo/Voyager/issues/34#issuecomment-1567007133), the URI Type was changed to \"Web\" and it resolved the problem. Feel free to attempt both URI Types to determine which one works for you. If all the approaches fail, please refer to the original tutorial in [minecraft-launcher-lib](https://minecraft-launcher-lib.readthedocs.io/en/stable/tutorial/microsoft_login.html).\nUpdate: This is probably a Microsoft's bug. See [issue #80 in minecraft-launcher-lib](https://codeberg.org/JakobDev/minecraft-launcher-lib/issues/80). If you cannot solve this problem, you can try to use the [Minecraft Offi", + "type": "code", + "location": "/FAQ.md:12-18" + }, + "5": { + "file_id": 0, + "content": "Code explains that the new URL contains necessary code like \"M.C...\" which should be copied and pasted. It also mentions a reported issue where 'KeyError: 'access_token'' occurs after copying the link, which was resolved by changing Redirect URI Type to \"Web\". If this fails, try original tutorial or it could be Microsoft's bug in reference #80.", + "type": "comment" + }, + "6": { + "file_id": 0, + "content": "cial Launcher](./installation/minecraft_instance_install.md#option-2-minecraft-official-launcher) and use mc_port to run.\n## I got `Subprocess Mineflayer failed to start` error.\nThere are many reasons that may cause this problem. You can try with following solutions:\n1. Make sure you install nodejs and the dependency packages correctly. You can use the following command to check your installation:\n ```bash\n cd voyager/env/mineflayer\n node index.js\n ```\n If you see `Server started on port {PORT}`, then your installation is correct. You can kill the process by `Ctrl+C`.\n2. Make sure you install Fabric correctly. You should be able to select the Fabric version in the Minecraft launcher. \n3. Each Mineflayer process can only listen to one port. If you want to start multiple instances of `Voyager`, you need to manually change the port when initialization:\n ```python\n from voyager import Voyager\n voyager = Voyager(\n server_port=3001, # default is 3000\n ...\n )\n ```\n## I saw the bot left and rejoin the game after each task.", + "type": "code", + "location": "/FAQ.md:18-38" + }, + "7": { + "file_id": 0, + "content": "The code provides solutions to resolve issues with Mineflayer, a bot used in the Voyager program. The first issue is ensuring proper installation of Node.js and dependencies, which can be checked by running a command in the specified directory. The second solution involves verifying the correct installation of Fabric and selecting the appropriate version in the Minecraft launcher. The third solution addresses running multiple instances of Voyager by manually changing the port during initialization.", + "type": "comment" + }, + "8": { + "file_id": 0, + "content": "After completing each task, we'll reset the environment, which means the bot will exit and rejoin the game. This reset is necessary to synchronize Mineflayer with the Minecraft game. We do this because certain commands we utilize might result in lag on the Mineflayer side, causing the inventory stored in Mineflayer to differ from the actual inventory in the game. However, if you wish to avoid the reset, you can use `voyager.learn(reset_env=False)` and consider increasing the `env_wait_ticks` value. This will provide Mineflayer with additional time to sync with the Minecraft game.\n## How to show the bot's first-person view?\nDue to the Mineflayer's limitation, we currently can not directly get the bot's view in the game. Although there's a plugin called [prismarine-viewer](https://github.com/PrismarineJS/prismarine-viewer), the video quality is not good enough, so we opt not to use it. Our demo video is generated by [replay-mod](https://www.replaymod.com/). We start the recording and let the bot play for hours, then come back to the recording and render the view from the bot.", + "type": "code", + "location": "/FAQ.md:40-45" + }, + "9": { + "file_id": 0, + "content": "Explanation: The code provides information about the necessity of resetting the environment after completing tasks to sync Mineflayer with the Minecraft game, and how to show the bot's first-person view. It also mentions the limitations of Mineflayer in directly getting the bot's view and a potential alternative (prismarine-viewer), but opts not to use it due to video quality issues. The demo video is generated by replay-mod, which records and renders the view from the bot after playing for hours.", + "type": "comment" + }, + "10": { + "file_id": 0, + "content": "## Can I use GPT-3.5 instead of GPT-4?\nIt's highly recommended to use GPT-4. GPT-3.5 falls behind in terms of code quality and reasoning ability compared to GPT-4. Moreover, GPT-3.5 has a limited context length, which means it may provide incomplete responses. If you insist on using GPT-3.5, it is essential to configure it with `skill_manager_retrieval_top_k` ≤ 2 to reduce the context length of the prompt.\n## What's the estimated cost of running Voyager?\nUsing Voyager for approximately 160 iterations using GPT-4 will cost you around 50 USD. It's important to keep a close eye on your OpenAI API expenses and avoid unnecessary spending. Once Voyager begins running, it's recommended to monitor the bot's behavior for a period and ensure that it successfully completes some tasks.", + "type": "code", + "location": "/FAQ.md:48-54" + }, + "11": { + "file_id": 0, + "content": "The code provides information about using GPT-4 instead of GPT-3.5, mentioning the benefits and the need to configure prompt length when using GPT-3.5. It also estimates the cost of running Voyager at approximately 50 USD for 160 iterations with GPT-4, advising to monitor expenses and bot behavior.", + "type": "comment" + }, + "12": { + "file_id": 1, + "content": "/README.md", + "type": "filepath" + }, + "13": { + "file_id": 1, + "content": "Voyager AI agent explores Minecraft, learns autonomously, outperforms SOTA, generalizes tasks, MIT licensed, expected 2023 Arxiv preprint (Arxiv-2305.16291).", + "type": "summary" + }, + "14": { + "file_id": 1, + "content": "# Voyager: An Open-Ended Embodied Agent with Large Language Models\n
\n[[Website]](https://voyager.minedojo.org/)\n[[Arxiv]](https://arxiv.org/abs/2305.16291)\n[[PDF]](https://voyager.minedojo.org/assets/documents/voyager.pdf)\n[[Tweet]](https://twitter.com/DrJimFan/status/1662115266933972993?s=20)\n[![Python Version](https://img.shields.io/badge/Python-3.9-blue.svg)](https://github.com/MineDojo/Voyager)\n[![GitHub license](https://img.shields.io/github/license/MineDojo/Voyager)](https://github.com/MineDojo/Voyager/blob/main/LICENSE)\n______________________________________________________________________\nhttps://github.com/MineDojo/Voyager/assets/25460983/ce29f45b-43a5-4399-8fd8-5dd105fd64f2\n![](images/pull.png)\n
\nWe introduce Voyager, the first LLM-powered embodied lifelong learning agent\nin Minecraft that continuously explores the world, acquires diverse skills, and\nmakes novel discoveries without human intervention. Voyager consists of three\nkey components: 1) an automatic curriculum that maximizes exploration, 2) an", + "type": "code", + "location": "/README.md:1-24" + }, + "15": { + "file_id": 1, + "content": "This code introduces Voyager, an embodied agent powered by large language models that continuously explores Minecraft, acquires skills, and discovers new things without human intervention. It consists of three main components: an automatic curriculum for exploration, a language model, and an interactive environment in Minecraft.", + "type": "comment" + }, + "16": { + "file_id": 1, + "content": "ever-growing skill library of executable code for storing and retrieving complex\nbehaviors, and 3) a new iterative prompting mechanism that incorporates environment\nfeedback, execution errors, and self-verification for program improvement.\nVoyager interacts with GPT-4 via blackbox queries, which bypasses the need for\nmodel parameter fine-tuning. The skills developed by Voyager are temporally\nextended, interpretable, and compositional, which compounds the agent’s abilities\nrapidly and alleviates catastrophic forgetting. Empirically, Voyager shows\nstrong in-context lifelong learning capability and exhibits exceptional proficiency\nin playing Minecraft. It obtains 3.3× more unique items, travels 2.3× longer\ndistances, and unlocks key tech tree milestones up to 15.3× faster than prior SOTA.\nVoyager is able to utilize the learned skill library in a new Minecraft world to\nsolve novel tasks from scratch, while other techniques struggle to generalize.\nIn this repo, we provide Voyager code. This codebase is under [MIT License](LICENSE).", + "type": "code", + "location": "/README.md:25-38" + }, + "17": { + "file_id": 1, + "content": "This code snippet describes Voyager's functionality: a skill library for storing and retrieving complex behaviors, an iterative prompting mechanism incorporating feedback, and interaction with GPT-4 through blackbox queries. It shows strong in-context lifelong learning capabilities, outperforming prior SOTA in Minecraft, generalizing to novel tasks from scratch. The codebase is under MIT License.", + "type": "comment" + }, + "18": { + "file_id": 1, + "content": "# Installation\nVoyager requires Python ≥ 3.9 and Node.js ≥ 16.13.0. We have tested on Ubuntu 20.04, Windows 11, and macOS. You need to follow the instructions below to install Voyager.\n## Python Install\n```\ngit clone https://github.com/MineDojo/Voyager\ncd Voyager\npip install -e .\n```\n## Node.js Install\nIn addition to the Python dependencies, you need to install the following Node.js packages:\n```\ncd voyager/env/mineflayer\nnpm install -g npx\nnpm install\ncd mineflayer-collectblock\nnpx tsc\ncd ..\nnpm install\n```\n## Minecraft Instance Install\nVoyager depends on Minecraft game. You need to install Minecraft game and set up a Minecraft instance.\nFollow the instructions in [Minecraft Login Tutorial](installation/minecraft_instance_install.md) to set up your Minecraft Instance.\n## Fabric Mods Install\nYou need to install fabric mods to support all the features in Voyager. Remember to use the correct Fabric version of all the mods. \nFollow the instructions in [Fabric Mods Install](installation/fabric_mods_install.md) to install the mods.", + "type": "code", + "location": "/README.md:40-72" + }, + "19": { + "file_id": 1, + "content": "This code provides instructions for installing Voyager, a tool that requires Python ≥ 3.9 and Node.js ≥ 16.13.0. It outlines steps for Python, Node.js, Minecraft instance, and Fabric mods installation with links to additional tutorials.", + "type": "comment" + }, + "20": { + "file_id": 1, + "content": "# Getting Started\nVoyager uses OpenAI's GPT-4 as the language model. You need to have an OpenAI API key to use Voyager. You can get one from [here](https://platform.openai.com/account/api-keys).\nAfter the installation process, you can run Voyager by:\n```python\nfrom voyager import Voyager\n# You can also use mc_port instead of azure_login, but azure_login is highly recommended\nazure_login = {\n \"client_id\": \"YOUR_CLIENT_ID\",\n \"redirect_url\": \"https://127.0.0.1/auth-response\",\n \"secret_value\": \"[OPTIONAL] YOUR_SECRET_VALUE\",\n \"version\": \"fabric-loader-0.14.18-1.19\", # the version Voyager is tested on\n}\nopenai_api_key = \"YOUR_API_KEY\"\nvoyager = Voyager(\n azure_login=azure_login,\n openai_api_key=openai_api_key,\n)\n# start lifelong learning\nvoyager.learn()\n```\n* If you are running with `Azure Login` for the first time, it will ask you to follow the command line instruction to generate a config file.\n* For `Azure Login`, you also need to select the world and open the world to LAN by yourself. After you run `voyager.learn()` the game will pop up soon, you need to:", + "type": "code", + "location": "/README.md:74-100" + }, + "21": { + "file_id": 1, + "content": "To use Voyager, ensure you have an OpenAI API key and install the required dependencies. Create an azure_login dictionary with your client ID, redirect URL, secret value (optional), and version. Set openai_api_key to YOUR_API_KEY. Instantiate Voyager with these settings. First-time Azure Login users will generate a config file following instructions. For Azure Login, open the world and LAN, then run learn(). The game will start soon.", + "type": "comment" + }, + "22": { + "file_id": 1, + "content": " 1. Select `Singleplayer` and press `Create New World`.\n 2. Set Game Mode to `Creative` and Difficulty to `Peaceful`.\n 3. After the world is created, press `Esc` key and press `Open to LAN`.\n 4. Select `Allow cheats: ON` and press `Start LAN World`. You will see the bot join the world soon. \n# Resume from a checkpoint during learning\nIf you stop the learning process and want to resume from a checkpoint later, you can instantiate Voyager by:\n```python\nfrom voyager import Voyager\nvoyager = Voyager(\n azure_login=azure_login,\n openai_api_key=openai_api_key,\n ckpt_dir=\"YOUR_CKPT_DIR\",\n resume=True,\n)\n```\n# Run Voyager for a specific task with a learned skill library\nIf you want to run Voyager for a specific task with a learned skill library, you should first pass the skill library directory to Voyager:\n```python\nfrom voyager import Voyager\n# First instantiate Voyager with skill_library_dir.\nvoyager = Voyager(\n azure_login=azure_login,\n openai_api_key=openai_api_key,\n skill_library_dir=\"./skill_library/trial1\", # Load a learned skill library.", + "type": "code", + "location": "/README.md:101-130" + }, + "23": { + "file_id": 1, + "content": "1. The code provides instructions to set up a Minecraft world for Voyager learning.\n2. Users can resume from a checkpoint or run Voyager with a specific skill library.\n3. Required credentials, directories and settings need to be provided when instantiating the Voyager class.", + "type": "comment" + }, + "24": { + "file_id": 1, + "content": " ckpt_dir=\"YOUR_CKPT_DIR\", # Feel free to use a new dir. Do not use the same dir as skill library because new events will still be recorded to ckpt_dir. \n resume=False, # Do not resume from a skill library because this is not learning.\n)\n```\nThen, you can run task decomposition. Notice: Occasionally, the task decomposition may not be logical. If you notice the printed sub-goals are flawed, you can rerun the decomposition.\n```python\n# Run task decomposition\ntask = \"YOUR TASK\" # e.g. \"Craft a diamond pickaxe\"\nsub_goals = voyager.decompose_task(task=task)\n```\nFinally, you can run the sub-goals with the learned skill library:\n```python\nvoyager.inference(sub_goals=sub_goals)\n```\nFor all valid skill libraries, see [Learned Skill Libraries](skill_library/README.md).\n# FAQ\nIf you have any questions, please check our [FAQ](FAQ.md) first before opening an issue.\n# Paper and Citation\nIf you find our work useful, please consider citing us! \n```bibtex\n@article{wang2023voyager,\n title = {Voyager: An Open-Ended Embodied Agent with Large Language Models},", + "type": "code", + "location": "/README.md:131-157" + }, + "25": { + "file_id": 1, + "content": "This code sets up the Voyager agent with a specified checkpoint directory and disables resuming from a skill library because this task is not learning-based. After setting up, it decomposes a given task into sub-goals using Voyager's task decomposition functionality. Finally, it runs inference on these sub-goals using the learned skill library. Valid skill libraries can be found at [Learned Skill Libraries](skill_library/README.md).", + "type": "comment" + }, + "26": { + "file_id": 1, + "content": " author = {Guanzhi Wang and Yuqi Xie and Yunfan Jiang and Ajay Mandlekar and Chaowei Xiao and Yuke Zhu and Linxi Fan and Anima Anandkumar},\n year = {2023},\n journal = {arXiv preprint arXiv: Arxiv-2305.16291}\n}\n```\nDisclaimer: This project is strictly for research purposes, and not an official product from NVIDIA.", + "type": "code", + "location": "/README.md:158-164" + }, + "27": { + "file_id": 1, + "content": "The code block represents the author, year, and journal information for a research paper. The paper is expected to be published in 2023 as an arXiv preprint with identifier Arxiv-2305.16291.", + "type": "comment" + }, + "28": { + "file_id": 2, + "content": "/installation/fabric_mods_install.md", + "type": "filepath" + }, + "29": { + "file_id": 2, + "content": "Summary: This code provides instructions for downloading Fabric Installer and JRE v17, installing mods, setting up respawn settings, and using fabric-loader for Minecraft gameplay.", + "type": "summary" + }, + "30": { + "file_id": 2, + "content": "# Fabric Mods Install\nIn this tutorial, we will install the Fabric launcher and 5 mods. Remember to use the correct Fabric version that matches your game version (1.19) of all the mods. \n1. You can download the latest Fabric Installer from [here](https://fabricmc.net/use/installer/). For Windows users, just download the `.exe` file. For Mac or Ubuntu users, download the jar file and call `java -jar fabric-installer-0.11.2.jar` to install. Select game version to be `1.19` and loader version to be `0.14.18`. It will automatically detect your Minecraft game install location.\n2. After installing Fabric, you will have a `YOUR_MINECRAFT_GAME_LOCATION/mods` folder. You need to put all the mods under this folder. Also, you will have a `YOUR_MINECRAFT_GAME_LOCATION/versions/fabric-loader-0.14.18-1.19`. This is the version you will run the game with. \n3. Here are 4 mods that can be directly downloaded to `YOUR_MINECRAFT_GAME_LOCATION/mods` folder: \n * [Fabric API](https://modrinth.com/mod/fabric-api/version/0.58.0+1.19): Basic Fabric APIs.", + "type": "code", + "location": "/installation/fabric_mods_install.md:1-6" + }, + "31": { + "file_id": 2, + "content": "Installing Fabric mods: Download Fabric Installer, select 1.19 version, place mods in mods folder, use fabric-loader-0.14.18-1.19 for gameplay.", + "type": "comment" + }, + "32": { + "file_id": 2, + "content": " * [Mod Menu](https://cdn.modrinth.com/data/mOgUt4GM/versions/4.0.4/modmenu-4.0.4.jar): Used to manage all the mods that you download.\n * [Complete Config](https://www.curseforge.com/minecraft/mc-mods/completeconfig/download/3821056): Dependency of server pause.\n * [Multi Server Pause](https://www.curseforge.com/minecraft/mc-mods/multiplayer-server-pause-fabric/download/3822586): Used to pause the server when waiting for GPT-4 to reply.\n4. For the last mod [Better Respawn](https://github.com/xieleo5/better-respawn/tree/1.19), you need to manually clone and compile.\n * After you clone the repo, remove the `'forge'` string in the last line of `settings.gradle`. Then run `gradlew build` to compile the mod. You will find the compiled jar file in `better-respawn/fabric/build/libs/better-respawn-fabric-1.19-2.0.0.jar`. Put the jar file to the mod folder.\n * You will need a Java Runtime Environment v17+ to build `better-respawn`. Some newer JRE versions will error during build. Find th", + "type": "code", + "location": "/installation/fabric_mods_install.md:7-13" + }, + "33": { + "file_id": 2, + "content": "The code provides a list of mods for Minecraft Fabric, including their descriptions and download links. It also outlines instructions for compiling the 'Better Respawn' mod manually, specifying version-specific changes to settings and build requirements.", + "type": "comment" + }, + "34": { + "file_id": 2, + "content": "e JRE v17 archive [here](https://www.oracle.com/java/technologies/javase/jdk17-archive-downloads.html).\n * After you launch the game, go to `YOUR_MINECRAFT_GAME_LOCATION/config/better-respawn`, and modify the properties file with:\n ```\n respawn_block_range=32\n max_respawn_distance=32\n min_respawn_distance=0\n ```\n5. Don't forget to change the `version` in `azure_login` to `fabric-loader-0.14.18-1.19` that you are using. You can find it under `YOUR_MINECRAFT_GAME_LOCATION/version` folder.\nYou can return to [README.md](../README.md#getting-started) and getting started now.", + "type": "code", + "location": "/installation/fabric_mods_install.md:13-22" + }, + "35": { + "file_id": 2, + "content": "Download JRE v17 archive, modify properties file with respawn settings, change `azure_login` version to `fabric-loader-0.14.18-1.19`, and return to README for getting started.", + "type": "comment" + }, + "36": { + "file_id": 3, + "content": "/installation/minecraft_instance_install.md", + "type": "filepath" + }, + "37": { + "file_id": 3, + "content": "This code sets up Azure login for Minecraft Voyager, requiring an Azure portal account and registering a new application with client_id, redirect_url, optional secret_value, and desired Minecraft version specified using fabric-loader.", + "type": "summary" + }, + "38": { + "file_id": 3, + "content": "# Minecraft Instance Install\nTo start using Voyager, you should first make sure to have an official [Minecraft](https://www.minecraft.net/) game (version 1.19) installed. \nThere are two ways to start a Minecraft instance for Voyager. Sometimes GPT-4 will write an infinite loop that runs forever. In this case, there'll be a request timeout. Using Azure login can automatically resume the running if there's a request timeout.\n## Option 1: Microsoft Azure Login (Recommended)\nUsing this method will allow Voyager to automatically resume when there's a request timeout. This is dependent on the [minecraft-launcher-lib](https://minecraft-launcher-lib.readthedocs.io/en/stable/tutorial/microsoft_login.html#let-the-user-log-in) library.\n1. Sign in to [Azure Portal](https://portal.azure.com/).\n2. Go to [Azure Active Directory](https://portal.azure.com/#blade/Microsoft_AAD_IAM/ActiveDirectoryMenuBlade/Overview).\n3. Click on the `App Registrations` tab on the left panel.\n4. Click on the `New registration` button.", + "type": "code", + "location": "/installation/minecraft_instance_install.md:1-12" + }, + "39": { + "file_id": 3, + "content": "Code snippet provides instructions to set up a Minecraft instance for Voyager, with the recommended method being Microsoft Azure Login. It outlines steps to create an Azure portal account, navigate to Active Directory, and register a new application.", + "type": "comment" + }, + "40": { + "file_id": 3, + "content": "5. Fill the form with the following values:\n - Name: `YOUR_APP_NAME`\n - Supported account types: `Accounts in any organizational directory (Any Azure AD directory - Multitenant) and personal Microsoft accounts`\n - Redirect URI Type: `Public client/native (mobile & desktop)`, Value: `https://127.0.0.1/auth-response` (If you get `KeyError: 'access_token'` in the end, you can try to change the type to `Web`, see [FAQ](https://github.com/MineDojo/Voyager/blob/main/FAQ.md) for more information)\n6. Click on the `Register` button.\n7. The `Application (client) ID` will be your `client_id`.\n8. [Optional] Go to the `Certificates & Secrets` tab and click on the `New client secret` button. Fill the description by yourself. After you click `Add`, you will see your value, this will be your `secret_value`.\n9. Go to your Minecraft install location `YOUR_MINECRAFT_GAME_LOCATION/versions`, and check all the versions you have. All the folder names are your valid `version` value. \nAfter these steps, you will finally get your azure_login information:", + "type": "code", + "location": "/installation/minecraft_instance_install.md:13-22" + }, + "41": { + "file_id": 3, + "content": "Fill out the form with provided values, register the application, obtain client ID and secret value, check Minecraft versions for valid version, then collect azure_login information.", + "type": "comment" + }, + "42": { + "file_id": 3, + "content": "```python\nazure_login = {\n \"client_id\": \"CLIENT_ID FROM STEP 7\",\n \"redirect_url\": \"https://127.0.0.1/auth-response\",\n \"secret_value\": \"[OPTIONAL] SECRET_KEY FROM STEP 8\",\n \"version\": \"MINECRAFT VERSION YOU WANT TO USE\",\n}\n```\n**Voyager use `fabric-loader-0.14.18-1.19` version to run all the experiments.** You may not have this version currently, you can move on to the [Fabric Mods Install](fabric_mods_install.md#fabric-mods-install) section and follow the instructions there to install the fabric version of the game.\n## Option 2: Minecraft Official Launcher\nAfter you install official Minecraft, you should have a Minecraft official launcher, open it, and follow the instructions here:\n1. Select the version you want to play and start the game.\n2. Select `Singleplayer` and create a new world.\n3. Set Game Mode to `Creative` and Difficulty to `Peaceful`.\n4. After the world is created, press `Esc` and select `Open to LAN`.\n5. Select `Allow cheats: ON` and press `Start LAN World`.\n6. You will see a port number in the chat log, that is your `mc-port`, use this number to instantiate Voyager later.", + "type": "code", + "location": "/installation/minecraft_instance_install.md:23-41" + }, + "43": { + "file_id": 3, + "content": "This code sets up the necessary parameters for logging into Azure to create an instance of Minecraft. The \"client_id\" refers to a specific ID from Step 7, the \"redirect_url\" is set to \"https://127.0.0.1/auth-response\", the \"secret_value\" is an optional secret key from Step 8, and the \"version\" refers to the Minecraft version desired for use. This specific code utilizes fabric-loader-0.14.18-1.19 to run all experiments.", + "type": "comment" + }, + "44": { + "file_id": 4, + "content": "/requirements.txt", + "type": "filepath" + }, + "45": { + "file_id": 4, + "content": "The code specifies the dependencies required for the project. It includes tqdm, langchain, javascript, setuptools, openai, chardet, cchardet, chromadb, tiktoken, requests, gymnasium, psutil, and minecraft_launcher_lib. These are essential libraries needed for the project's functionality.", + "type": "summary" + }, + "46": { + "file_id": 4, + "content": "tqdm\nlangchain\njavascript\nsetuptools\nopenai\nchardet\ncchardet\nchromadb==0.3.29\ntiktoken\nrequests\nsetuptools\ngymnasium\npsutil\nminecraft_launcher_lib", + "type": "code", + "location": "/requirements.txt:1-14" + }, + "47": { + "file_id": 4, + "content": "The code specifies the dependencies required for the project. It includes tqdm, langchain, javascript, setuptools, openai, chardet, cchardet, chromadb, tiktoken, requests, gymnasium, psutil, and minecraft_launcher_lib. These are essential libraries needed for the project's functionality.", + "type": "comment" + }, + "48": { + "file_id": 5, + "content": "/setup.py", + "type": "filepath" + }, + "49": { + "file_id": 5, + "content": "This code sets up a Python package using setup.py, defining essential properties like name, requirements, and extras while adhering to given classifiers for development status, topic, and language.", + "type": "summary" + }, + "50": { + "file_id": 5, + "content": "import os\nimport pathlib\nimport pkg_resources\nfrom setuptools import setup, find_packages\nPKG_NAME = \"voyager\"\nVERSION = \"0.1\"\nEXTRAS = {}\ndef _read_file(fname):\n # this_dir = os.path.abspath(os.path.dirname(__file__))\n # with open(os.path.join(this_dir, fname)) as f:\n with pathlib.Path(fname).open(encoding=\"utf-8\") as fp:\n return fp.read()\ndef _read_install_requires():\n with pathlib.Path(\"requirements.txt\").open() as fp:\n return [\n str(requirement) for requirement in pkg_resources.parse_requirements(fp)\n ]\ndef _fill_extras(extras):\n if extras:\n extras[\"all\"] = list(set([item for group in extras.values() for item in group]))\n return extras\nsetup(\n name=PKG_NAME,\n version=VERSION,\n author=f\"MineDojo Team\",\n url=\"https://github.com/MineDojo/Voyager\",\n description=\"research project\",\n long_description=_read_file(\"README.md\"),\n long_description_content_type=\"text/markdown\",\n keywords=[\n \"Open-Ended Learning\",\n \"Lifelong Learning\",", + "type": "code", + "location": "/setup.py:1-42" + }, + "51": { + "file_id": 5, + "content": "This code is setting up the Voyager Python package. It imports necessary modules, defines constants like PKG_NAME and VERSION, reads the installation requirements from a \"requirements.txt\" file, fills in extras if needed, and finally sets up the package using setup() function with various properties.", + "type": "comment" + }, + "52": { + "file_id": 5, + "content": " \"Embodied Agents\",\n \"Large Language Models\",\n ],\n license=\"MIT License\",\n packages=find_packages(include=f\"{PKG_NAME}.*\"),\n include_package_data=True,\n zip_safe=False,\n install_requires=_read_install_requires(),\n extras_require=_fill_extras(EXTRAS),\n python_requires=\">=3.9\",\n classifiers=[\n \"Development Status :: 5 - Production/Stable\",\n \"Topic :: Scientific/Engineering :: Artificial Intelligence\",\n \"Environment :: Console\",\n \"Programming Language :: Python :: 3.9\",\n ],\n)", + "type": "code", + "location": "/setup.py:43-59" + }, + "53": { + "file_id": 5, + "content": "This code is for setting up a Python package using setup.py. It includes the package name, author, description, license, packages to include, installation requirements, and extras. The classifiers specify development status, topic, and programming language.", + "type": "comment" + }, + "54": { + "file_id": 6, + "content": "/skill_library/README.md", + "type": "filepath" + }, + "55": { + "file_id": 6, + "content": "The code introduces the Skill Library and its community contributions for a project, providing instructions on resuming from a community contribution and contributing after running the learning process. It also outlines how to share the learned skill library.", + "type": "summary" + }, + "56": { + "file_id": 6, + "content": "# Learned Skill Libraries\n## Ours\n* [skill_library/trial1](trial1)\n* [skill_library/trial2](trial2)\n* [skill_library/trial3](trial3)\n## Community Contributions\n* [daswer123/Voyager_checkpoint](https://github.com/daswer123/Voyager_checkpoint)\n* [swen128/Voyager_checkpoint](https://github.com/swen128/Voyager_checkpoint)\n* [DeveloperHarris/voyager_checkpoint](https://github.com/DeveloperHarris/voyager_checkpoint)\n### How to resume from a community contribution\nFirst, you need to clone or download their repo. Then, the resume is the same as using ours skill libraries. Just set `skill_library_dir=COMMUNITY_CKPT_DIR` where `COMMUNITY_CKPT_DIR` is the ckpt dir inside the folder you just downloaded.\n## How to Contribute\nAfter you run the learning process, you will see a checkpoint directory like:\n```\n.\n├── action\n│ └── chest_memory.json\n├── curriculum\n│ ├── completed_tasks.json\n│ ├── failed_tasks.json\n│ ├── qa_cache.json\n│ └── vectordb\n├── events\n└── skill\n ├── code\n │ ├── catchThreeFishWithCheck.js\n │ ├── collectBamboo.js", + "type": "code", + "location": "/skill_library/README.md:1-34" + }, + "57": { + "file_id": 6, + "content": "This code introduces the Skill Library and its community contributions for a project. It explains how to resume from a community contribution by cloning or downloading their repository and setting the skill library directory accordingly. It also provides information on how to contribute after running the learning process, resulting in a checkpoint directory with various files.", + "type": "comment" + }, + "58": { + "file_id": 6, + "content": " │ ├── ...\n ├── description\n │ ├── catchThreeFishWithCheck.txt\n │ ├── collectBamboo.txt\n │ └── ...\n ├── skills.json\n └── vectordb\n```\nOnly `YOUR_CKPT_DIR/skill` is a learned skill library, which you can share with others. Create a pull request and add your skill library link to this page.", + "type": "code", + "location": "/skill_library/README.md:35-44" + }, + "59": { + "file_id": 6, + "content": "The code represents a skill library containing multiple text files, a JSON file, and a vector database. Only the `YOUR_CKPT_DIR/skill` directory is a learned skill library that can be shared with others by creating a pull request and adding the link to this page.", + "type": "comment" + }, + "60": { + "file_id": 7, + "content": "/voyager/__init__.py", + "type": "filepath" + }, + "61": { + "file_id": 7, + "content": "This line imports the Voyager class from the voyager module within the same package, allowing access to its methods and attributes.", + "type": "summary" + }, + "62": { + "file_id": 7, + "content": "from .voyager import Voyager", + "type": "code", + "location": "/voyager/__init__.py:1-1" + }, + "63": { + "file_id": 7, + "content": "This line imports the Voyager class from the voyager module within the same package, allowing access to its methods and attributes.", + "type": "comment" + }, + "64": { + "file_id": 8, + "content": "/voyager/agents/__init__.py", + "type": "filepath" + }, + "65": { + "file_id": 8, + "content": "This code imports different agents from their respective modules (action, critic, curriculum) and SkillManager for Voyager's agent functionality.", + "type": "summary" + }, + "66": { + "file_id": 8, + "content": "from .action import ActionAgent\nfrom .critic import CriticAgent\nfrom .curriculum import CurriculumAgent\nfrom .skill import SkillManager", + "type": "code", + "location": "/voyager/agents/__init__.py:1-4" + }, + "67": { + "file_id": 8, + "content": "This code imports different agents from their respective modules (action, critic, curriculum) and SkillManager for Voyager's agent functionality.", + "type": "comment" + }, + "68": { + "file_id": 9, + "content": "/voyager/agents/action.py", + "type": "filepath" + }, + "69": { + "file_id": 9, + "content": "The ActionAgent class handles agent behavior, using ChatOpenAI for language processing and offering options like chat log and error handling. It defines methods to update chest memory, render observations, and send actions, while processing game events, constructing observation strings, handling inventory, checking chest involvement, and executing async functions with error handling and item filtering.", + "type": "summary" + }, + "70": { + "file_id": 9, + "content": "import re\nimport time\nimport voyager.utils as U\nfrom javascript import require\nfrom langchain.chat_models import ChatOpenAI\nfrom langchain.prompts import SystemMessagePromptTemplate\nfrom langchain.schema import AIMessage, HumanMessage, SystemMessage\nfrom voyager.prompts import load_prompt\nfrom voyager.control_primitives_context import load_control_primitives_context\nclass ActionAgent:\n def __init__(\n self,\n model_name=\"gpt-3.5-turbo\",\n temperature=0,\n request_timout=120,\n ckpt_dir=\"ckpt\",\n resume=False,\n chat_log=True,\n execution_error=True,\n ):\n self.ckpt_dir = ckpt_dir\n self.chat_log = chat_log\n self.execution_error = execution_error\n U.f_mkdir(f\"{ckpt_dir}/action\")\n if resume:\n print(f\"\\033[32mLoading Action Agent from {ckpt_dir}/action\\033[0m\")\n self.chest_memory = U.load_json(f\"{ckpt_dir}/action/chest_memory.json\")\n else:\n self.chest_memory = {}\n self.llm = ChatOpenAI(", + "type": "code", + "location": "/voyager/agents/action.py:1-34" + }, + "71": { + "file_id": 9, + "content": "Initializing the ActionAgent class by setting its attributes and loading the necessary files. This class is used to control agent behavior, utilizing the ChatOpenAI language model for generating responses. It also has options for chat log, execution error handling, and saving/loading checkpoints for state persistence.", + "type": "comment" + }, + "72": { + "file_id": 9, + "content": " model_name=model_name,\n temperature=temperature,\n request_timeout=request_timout,\n )\n def update_chest_memory(self, chests):\n for position, chest in chests.items():\n if position in self.chest_memory:\n if isinstance(chest, dict):\n self.chest_memory[position] = chest\n if chest == \"Invalid\":\n print(\n f\"\\033[32mAction Agent removing chest {position}: {chest}\\033[0m\"\n )\n self.chest_memory.pop(position)\n else:\n if chest != \"Invalid\":\n print(f\"\\033[32mAction Agent saving chest {position}: {chest}\\033[0m\")\n self.chest_memory[position] = chest\n U.dump_json(self.chest_memory, f\"{self.ckpt_dir}/action/chest_memory.json\")\n def render_chest_observation(self):\n chests = []\n for chest_position, chest in self.chest_memory.items():\n if isinstance(chest, dict) and len(chest) > 0:", + "type": "code", + "location": "/voyager/agents/action.py:35-59" + }, + "73": { + "file_id": 9, + "content": "The code defines a class with methods for updating the agent's chest memory, rendering chest observations, and sending actions. It initializes an action agent using a model_name, temperature, and request_timeout. The update_chest_memory method checks if the chest already exists in the chest_memory dictionary and updates or removes it accordingly. If not present, it saves the new chest. Finally, the render_chest_observation method creates a list of chests from the chest memory and returns it.", + "type": "comment" + }, + "74": { + "file_id": 9, + "content": " chests.append(f\"{chest_position}: {chest}\")\n for chest_position, chest in self.chest_memory.items():\n if isinstance(chest, dict) and len(chest) == 0:\n chests.append(f\"{chest_position}: Empty\")\n for chest_position, chest in self.chest_memory.items():\n if isinstance(chest, str):\n assert chest == \"Unknown\"\n chests.append(f\"{chest_position}: Unknown items inside\")\n assert len(chests) == len(self.chest_memory)\n if chests:\n chests = \"\\n\".join(chests)\n return f\"Chests:\\n{chests}\\n\\n\"\n else:\n return f\"Chests: None\\n\\n\"\n def render_system_message(self, skills=[]):\n system_template = load_prompt(\"action_template\")\n # FIXME: Hardcoded control_primitives\n base_skills = [\n \"exploreUntil\",\n \"mineBlock\",\n \"craftItem\",\n \"placeItem\",\n \"smeltItem\",\n \"killMob\",\n ]\n if not self.llm.model_name == \"gpt-3.5-turbo\":", + "type": "code", + "location": "/voyager/agents/action.py:60-86" + }, + "75": { + "file_id": 9, + "content": "This code snippet is a part of the 'action.py' file in the Voyager project. It seems to be responsible for fetching information about chests from a data structure called 'chest_memory'. If a chest is empty, its status is updated accordingly. The function also checks if there are any unknown items inside a chest and updates its status as well. Finally, it returns the information on all chests found in the format of \"Chests: \". If no chests are found, it returns \"Chests: None\". Additionally, there's a comment indicating that this code may not work with a specific model_name (gpt-3.5-turbo).", + "type": "comment" + }, + "76": { + "file_id": 9, + "content": " base_skills += [\n \"useChest\",\n \"mineflayer\",\n ]\n programs = \"\\n\\n\".join(load_control_primitives_context(base_skills) + skills)\n response_format = load_prompt(\"action_response_format\")\n system_message_prompt = SystemMessagePromptTemplate.from_template(\n system_template\n )\n system_message = system_message_prompt.format(\n programs=programs, response_format=response_format\n )\n assert isinstance(system_message, SystemMessage)\n return system_message\n def render_human_message(\n self, *, events, code=\"\", task=\"\", context=\"\", critique=\"\"\n ):\n chat_messages = []\n error_messages = []\n # FIXME: damage_messages is not used\n damage_messages = []\n assert events[-1][0] == \"observe\", \"Last event must be observe\"\n for i, (event_type, event) in enumerate(events):\n if event_type == \"onChat\":\n chat_messages.append(event[\"onChat\"])", + "type": "code", + "location": "/voyager/agents/action.py:87-112" + }, + "77": { + "file_id": 9, + "content": "The code initializes system message components by joining base skills with additional skills, loading the response format template, creating a system message prompt, and formatting the system message with the joined programs and response format. The render_human_message function creates chat messages, error messages, and damage messages based on event types. It asserts that the last event is \"observe\".", + "type": "comment" + }, + "78": { + "file_id": 9, + "content": " elif event_type == \"onError\":\n error_messages.append(event[\"onError\"])\n elif event_type == \"onDamage\":\n damage_messages.append(event[\"onDamage\"])\n elif event_type == \"observe\":\n biome = event[\"status\"][\"biome\"]\n time_of_day = event[\"status\"][\"timeOfDay\"]\n voxels = event[\"voxels\"]\n entities = event[\"status\"][\"entities\"]\n health = event[\"status\"][\"health\"]\n hunger = event[\"status\"][\"food\"]\n position = event[\"status\"][\"position\"]\n equipment = event[\"status\"][\"equipment\"]\n inventory_used = event[\"status\"][\"inventoryUsed\"]\n inventory = event[\"inventory\"]\n assert i == len(events) - 1, \"observe must be the last event\"\n observation = \"\"\n if code:\n observation += f\"Code from the last round:\\n{code}\\n\\n\"\n else:\n observation += f\"Code from the last round: No code in the first round\\n\\n\"", + "type": "code", + "location": "/voyager/agents/action.py:113-135" + }, + "79": { + "file_id": 9, + "content": "This code handles different event types in a game agent. It appends error and damage messages, collects observe data like biome, time of day, voxels, entities, health, hunger, position, equipment, inventory used, and the inventory itself. Then it constructs an observation string based on if there's code from the last round or not.", + "type": "comment" + }, + "80": { + "file_id": 9, + "content": " if self.execution_error:\n if error_messages:\n error = \"\\n\".join(error_messages)\n observation += f\"Execution error:\\n{error}\\n\\n\"\n else:\n observation += f\"Execution error: No error\\n\\n\"\n if self.chat_log:\n if chat_messages:\n chat_log = \"\\n\".join(chat_messages)\n observation += f\"Chat log: {chat_log}\\n\\n\"\n else:\n observation += f\"Chat log: None\\n\\n\"\n observation += f\"Biome: {biome}\\n\\n\"\n observation += f\"Time: {time_of_day}\\n\\n\"\n if voxels:\n observation += f\"Nearby blocks: {', '.join(voxels)}\\n\\n\"\n else:\n observation += f\"Nearby blocks: None\\n\\n\"\n if entities:\n nearby_entities = [\n k for k, v in sorted(entities.items(), key=lambda x: x[1])\n ]\n observation += f\"Nearby entities (nearest to farthest): {', '.join(nearby_entities)}\\n\\n\"\n else:\n observation += f\"Nearby entities (nearest to farthest): None\\n\\n\"", + "type": "code", + "location": "/voyager/agents/action.py:137-166" + }, + "81": { + "file_id": 9, + "content": "This code snippet is part of a larger program that appears to be an AI agent. It generates an observation for the agent, including information about execution errors, chat logs, biome, time of day, nearby blocks and entities. The observation is constructed by iterating through various lists, joining them with newlines, and appending them to the \"observation\" string variable. If a list is empty, a corresponding message is added to the observation.", + "type": "comment" + }, + "82": { + "file_id": 9, + "content": " observation += f\"Health: {health:.1f}/20\\n\\n\"\n observation += f\"Hunger: {hunger:.1f}/20\\n\\n\"\n observation += f\"Position: x={position['x']:.1f}, y={position['y']:.1f}, z={position['z']:.1f}\\n\\n\"\n observation += f\"Equipment: {equipment}\\n\\n\"\n if inventory:\n observation += f\"Inventory ({inventory_used}/36): {inventory}\\n\\n\"\n else:\n observation += f\"Inventory ({inventory_used}/36): Empty\\n\\n\"\n if not (\n task == \"Place and deposit useless items into a chest\"\n or task.startswith(\"Deposit useless items into the chest at\")\n ):\n observation += self.render_chest_observation()\n observation += f\"Task: {task}\\n\\n\"\n if context:\n observation += f\"Context: {context}\\n\\n\"\n else:\n observation += f\"Context: None\\n\\n\"\n if critique:\n observation += f\"Critique: {critique}\\n\\n\"\n else:\n observation += f\"Critique: None\\n\\n\"\n return HumanMessage(content=observation)", + "type": "code", + "location": "/voyager/agents/action.py:168-199" + }, + "83": { + "file_id": 9, + "content": "This code generates an observation string containing the agent's health, hunger, position, equipment, inventory, and task. It also checks if a chest is involved in the current task, adds chest information if necessary, and includes context and critique if available. The final result is returned as a HumanMessage object.", + "type": "comment" + }, + "84": { + "file_id": 9, + "content": " def process_ai_message(self, message):\n assert isinstance(message, AIMessage)\n retry = 3\n error = None\n while retry > 0:\n try:\n babel = require(\"@babel/core\")\n babel_generator = require(\"@babel/generator\").default\n code_pattern = re.compile(r\"```(?:javascript|js)(.*?)```\", re.DOTALL)\n code = \"\\n\".join(code_pattern.findall(message.content))\n parsed = babel.parse(code)\n functions = []\n assert len(list(parsed.program.body)) > 0, \"No functions found\"\n for i, node in enumerate(parsed.program.body):\n if node.type != \"FunctionDeclaration\":\n continue\n node_type = (\n \"AsyncFunctionDeclaration\"\n if node[\"async\"]\n else \"FunctionDeclaration\"\n )\n functions.append(\n {\n \"name\": node.id.name,", + "type": "code", + "location": "/voyager/agents/action.py:201-226" + }, + "85": { + "file_id": 9, + "content": "The code defines a function process_ai_message, which takes an AIMessage object as input. It checks if the message is of correct type, then enters a while loop to handle potential errors. Inside the loop, it imports required libraries and compiles the JavaScript/JS code from the message content using Babel. It parses the compiled code and extracts function declarations from the program's body. If no functions are found, it raises an assertion error. The extracted functions are then stored in a list. This function seems to be part of an AI agent for processing messages containing JavaScript/JS code snippets.", + "type": "comment" + }, + "86": { + "file_id": 9, + "content": " \"type\": node_type,\n \"body\": babel_generator(node).code,\n \"params\": list(node[\"params\"]),\n }\n )\n # find the last async function\n main_function = None\n for function in reversed(functions):\n if function[\"type\"] == \"AsyncFunctionDeclaration\":\n main_function = function\n break\n assert (\n main_function is not None\n ), \"No async function found. Your main function must be async.\"\n assert (\n len(main_function[\"params\"]) == 1\n and main_function[\"params\"][0].name == \"bot\"\n ), f\"Main function {main_function['name']} must take a single argument named 'bot'\"\n program_code = \"\\n\\n\".join(function[\"body\"] for function in functions)\n exec_code = f\"await {main_function['name']}(bot);\"", + "type": "code", + "location": "/voyager/agents/action.py:227-246" + }, + "87": { + "file_id": 9, + "content": "This code finds the last async function in a list of functions, asserts that there is one and it takes a single argument named \"bot\", then joins the bodies of all functions into program_code. It then creates an executable line of code with the main_function name and awaits its execution.", + "type": "comment" + }, + "88": { + "file_id": 9, + "content": " return {\n \"program_code\": program_code,\n \"program_name\": main_function[\"name\"],\n \"exec_code\": exec_code,\n }\n except Exception as e:\n retry -= 1\n error = e\n time.sleep(1)\n return f\"Error parsing action response (before program execution): {error}\"\n def summarize_chatlog(self, events):\n def filter_item(message: str):\n craft_pattern = r\"I cannot make \\w+ because I need: (.*)\"\n craft_pattern2 = (\n r\"I cannot make \\w+ because there is no crafting table nearby\"\n )\n mine_pattern = r\"I need at least a (.*) to mine \\w+!\"\n if re.match(craft_pattern, message):\n return re.match(craft_pattern, message).groups()[0]\n elif re.match(craft_pattern2, message):\n return \"a nearby crafting table\"\n elif re.match(mine_pattern, message):\n return re.match(mine_pattern, message).groups()[0]", + "type": "code", + "location": "/voyager/agents/action.py:247-270" + }, + "89": { + "file_id": 9, + "content": "The code defines a function that parses action responses and filters chat log messages for specific item requirements before program execution. It also includes error handling and retry mechanisms in case of exceptions during parsing. The chat log filtering uses regular expressions to match patterns and extract the required items.", + "type": "comment" + }, + "90": { + "file_id": 9, + "content": " else:\n return \"\"\n chatlog = set()\n for event_type, event in events:\n if event_type == \"onChat\":\n item = filter_item(event[\"onChat\"])\n if item:\n chatlog.add(item)\n return \"I also need \" + \", \".join(chatlog) + \".\" if chatlog else \"\"", + "type": "code", + "location": "/voyager/agents/action.py:271-280" + }, + "91": { + "file_id": 9, + "content": "Code checks for events of type \"onChat\", filters the items, and adds them to chatlog. If chatlog is not empty, returns a message mentioning required items. Otherwise, returns an empty string.", + "type": "comment" + }, + "92": { + "file_id": 10, + "content": "/voyager/agents/critic.py", + "type": "filepath" + }, + "93": { + "file_id": 10, + "content": "The CriticAgent class generates system and human messages, evaluates agent status based on mode (\"manual\" or \"auto\"), and checks task success using user or AI input. It is part of the Voyager project.", + "type": "summary" + }, + "94": { + "file_id": 10, + "content": "from voyager.prompts import load_prompt\nfrom voyager.utils.json_utils import fix_and_parse_json\nfrom langchain.chat_models import ChatOpenAI\nfrom langchain.schema import HumanMessage, SystemMessage\nclass CriticAgent:\n def __init__(\n self,\n model_name=\"gpt-3.5-turbo\",\n temperature=0,\n request_timout=120,\n mode=\"auto\",\n ):\n self.llm = ChatOpenAI(\n model_name=model_name,\n temperature=temperature,\n request_timeout=request_timout,\n )\n assert mode in [\"auto\", \"manual\"]\n self.mode = mode\n def render_system_message(self):\n system_message = SystemMessage(content=load_prompt(\"critic\"))\n return system_message\n def render_human_message(self, *, events, task, context, chest_observation):\n assert events[-1][0] == \"observe\", \"Last event must be observe\"\n biome = events[-1][1][\"status\"][\"biome\"]\n time_of_day = events[-1][1][\"status\"][\"timeOfDay\"]\n voxels = events[-1][1][\"voxels\"]\n health = events[-1][1][\"status\"][\"health\"]", + "type": "code", + "location": "/voyager/agents/critic.py:1-32" + }, + "95": { + "file_id": 10, + "content": "The code defines a class named `CriticAgent`, which initializes a ChatOpenAI language model and provides methods for rendering system and human messages based on events, task, context, and chest observation. The agent's mode can be set to \"auto\" or \"manual\".", + "type": "comment" + }, + "96": { + "file_id": 10, + "content": " hunger = events[-1][1][\"status\"][\"food\"]\n position = events[-1][1][\"status\"][\"position\"]\n equipment = events[-1][1][\"status\"][\"equipment\"]\n inventory_used = events[-1][1][\"status\"][\"inventoryUsed\"]\n inventory = events[-1][1][\"inventory\"]\n for i, (event_type, event) in enumerate(events):\n if event_type == \"onError\":\n print(f\"\\033[31mCritic Agent: Error occurs {event['onError']}\\033[0m\")\n return None\n observation = \"\"\n observation += f\"Biome: {biome}\\n\\n\"\n observation += f\"Time: {time_of_day}\\n\\n\"\n if voxels:\n observation += f\"Nearby blocks: {', '.join(voxels)}\\n\\n\"\n else:\n observation += f\"Nearby blocks: None\\n\\n\"\n observation += f\"Health: {health:.1f}/20\\n\\n\"\n observation += f\"Hunger: {hunger:.1f}/20\\n\\n\"\n observation += f\"Position: x={position['x']:.1f}, y={position['y']:.1f}, z={position['z']:.1f}\\n\\n\"\n observation += f\"Equipment: {equipment}\\n\\n\"", + "type": "code", + "location": "/voyager/agents/critic.py:33-60" + }, + "97": { + "file_id": 10, + "content": "This code is capturing the agent's current status and events to construct an observation for evaluation. It checks for errors, retrieves biome, time of day, nearby blocks, health, hunger, and position information from events, and formats them into a string.", + "type": "comment" + }, + "98": { + "file_id": 10, + "content": " if inventory:\n observation += f\"Inventory ({inventory_used}/36): {inventory}\\n\\n\"\n else:\n observation += f\"Inventory ({inventory_used}/36): Empty\\n\\n\"\n observation += chest_observation\n observation += f\"Task: {task}\\n\\n\"\n if context:\n observation += f\"Context: {context}\\n\\n\"\n else:\n observation += f\"Context: None\\n\\n\"\n print(f\"\\033[31m****Critic Agent human message****\\n{observation}\\033[0m\")\n return HumanMessage(content=observation)\n def human_check_task_success(self):\n confirmed = False\n success = False\n critique = \"\"\n while not confirmed:\n success = input(\"Success? (y/n)\")\n success = success.lower() == \"y\"\n critique = input(\"Enter your critique:\")\n print(f\"Success: {success}\\nCritique: {critique}\")\n confirmed = input(\"Confirm? (y/n)\") in [\"y\", \"\"]\n return success, critique\n def ai_check_task_success(self, messages, max_retries=5):", + "type": "code", + "location": "/voyager/agents/critic.py:62-91" + }, + "99": { + "file_id": 10, + "content": "The code snippet contains two functions: \"human_check_task_success\" and \"ai_check_task_success\". The first function prompts the user to confirm if a task is successful and also asks for their critique. It repeats until the user confirms their response. The second function automatically determines whether a task was successful or not, based on the input provided by the AI system. Both functions return either True or False for success, along with the user's critique.", + "type": "comment" + } +} \ No newline at end of file diff --git a/docs/data/1.json b/docs/data/1.json new file mode 100644 index 00000000..fe1a0d1e --- /dev/null +++ b/docs/data/1.json @@ -0,0 +1,543 @@ +{ + "100": { + "file_id": 10, + "content": " if max_retries == 0:\n print(\n \"\\033[31mFailed to parse Critic Agent response. Consider updating your prompt.\\033[0m\"\n )\n return False, \"\"\n if messages[1] is None:\n return False, \"\"\n critic = self.llm(messages).content\n print(f\"\\033[31m****Critic Agent ai message****\\n{critic}\\033[0m\")\n try:\n response = fix_and_parse_json(critic)\n assert response[\"success\"] in [True, False]\n if \"critique\" not in response:\n response[\"critique\"] = \"\"\n return response[\"success\"], response[\"critique\"]\n except Exception as e:\n print(f\"\\033[31mError parsing critic response: {e} Trying again!\\033[0m\")\n return self.ai_check_task_success(\n messages=messages,\n max_retries=max_retries - 1,\n )\n def check_task_success(\n self, *, events, task, context, chest_observation, max_retries=5\n ):\n human_message = self.render_human_message(", + "type": "code", + "location": "/voyager/agents/critic.py:92-119" + }, + "101": { + "file_id": 10, + "content": "The code checks the Critic Agent's response and tries to parse it as JSON. If parsing fails, it prints an error message and retries up to a specified number of times. If the \"success\" field is not present in the parsed response or if \"critique\" is missing, default values are set before returning success and critique.", + "type": "comment" + }, + "102": { + "file_id": 10, + "content": " events=events,\n task=task,\n context=context,\n chest_observation=chest_observation,\n )\n messages = [\n self.render_system_message(),\n human_message,\n ]\n if self.mode == \"manual\":\n return self.human_check_task_success()\n elif self.mode == \"auto\":\n return self.ai_check_task_success(\n messages=messages, max_retries=max_retries\n )\n else:\n raise ValueError(f\"Invalid critic agent mode: {self.mode}\")", + "type": "code", + "location": "/voyager/agents/critic.py:120-138" + }, + "103": { + "file_id": 10, + "content": "This code is a part of the critic agent class in the Voyager project. It initializes an instance with given events, task, context, and chest_observation. Then it creates a list of messages containing system and human messages. If the mode is set to \"manual\", it calls the human_check_task_success() function, otherwise if the mode is \"auto\", it calls ai_check_task_success() with the messages and max_retries as arguments. For any other invalid mode, it raises a ValueError.", + "type": "comment" + }, + "104": { + "file_id": 11, + "content": "/voyager/agents/curriculum.py", + "type": "filepath" + }, + "105": { + "file_id": 11, + "content": "The Curriculum Agent uses LangChain's ChatOpenAI and QA_ChatOpenAI for task-based learning, managing cache and tasks, storing info, calculating progress, generating observations during warm-up phase, and includes inventory usage checks. This AI game agent code handles QA interactions with the user, generates messages, asks questions, utilizes QA LLM for responses, processes question-concept pairs, and runs QA step 2 to answer questions.", + "type": "summary" + }, + "106": { + "file_id": 11, + "content": "from __future__ import annotations\nimport random\nimport re\nimport voyager.utils as U\nfrom voyager.prompts import load_prompt\nfrom voyager.utils.json_utils import fix_and_parse_json\nfrom langchain.chat_models import ChatOpenAI\nfrom langchain.embeddings.openai import OpenAIEmbeddings\nfrom langchain.schema import HumanMessage, SystemMessage\nfrom langchain.vectorstores import Chroma\nclass CurriculumAgent:\n def __init__(\n self,\n model_name=\"gpt-3.5-turbo\",\n temperature=0,\n qa_model_name=\"gpt-3.5-turbo\",\n qa_temperature=0,\n request_timout=120,\n ckpt_dir=\"ckpt\",\n resume=False,\n mode=\"auto\",\n warm_up=None,\n core_inventory_items: str | None = None,\n ):\n self.llm = ChatOpenAI(\n model_name=model_name,\n temperature=temperature,\n request_timeout=request_timout,\n )\n self.qa_llm = ChatOpenAI(\n model_name=qa_model_name,\n temperature=qa_temperature,\n request_timeout=request_timout,", + "type": "code", + "location": "/voyager/agents/curriculum.py:1-37" + }, + "107": { + "file_id": 11, + "content": "Class \"CurriculumAgent\" initializes LangChain's ChatOpenAI and QA_ChatOpenAI for language generation, with various optional parameters like model name, temperature, timeout, resume, mode etc. These LLMs (Language Generation Models) will be used to handle user queries or requests in the curriculum-based agent.", + "type": "comment" + }, + "108": { + "file_id": 11, + "content": " )\n assert mode in [\n \"auto\",\n \"manual\",\n ], f\"mode {mode} not supported\"\n self.mode = mode\n self.ckpt_dir = ckpt_dir\n U.f_mkdir(f\"{ckpt_dir}/curriculum/vectordb\")\n if resume:\n print(f\"\\033[35mLoading Curriculum Agent from {ckpt_dir}/curriculum\\033[0m\")\n self.completed_tasks = U.load_json(\n f\"{ckpt_dir}/curriculum/completed_tasks.json\"\n )\n self.failed_tasks = U.load_json(f\"{ckpt_dir}/curriculum/failed_tasks.json\")\n self.qa_cache = U.load_json(f\"{ckpt_dir}/curriculum/qa_cache.json\")\n else:\n self.completed_tasks = []\n self.failed_tasks = []\n self.qa_cache = {}\n # vectordb for qa cache\n self.qa_cache_questions_vectordb = Chroma(\n collection_name=\"qa_cache_questions_vectordb\",\n embedding_function=OpenAIEmbeddings(),\n persist_directory=f\"{ckpt_dir}/curriculum/vectordb\",\n )\n assert self.qa_cache_questions_vectordb._collection.count() == len(", + "type": "code", + "location": "/voyager/agents/curriculum.py:38-63" + }, + "109": { + "file_id": 11, + "content": "This code initializes a Curriculum Agent. It checks the mode (auto or manual), sets the checkpoint directory, and creates a curriculum/vectordb directory if it does not exist. If resuming from a checkpoint, it loads completed tasks, failed tasks, and QA cache from JSON files. Otherwise, it initializes empty lists for these tasks and an empty dictionary for the QA cache. The code then creates a Chroma vector database named \"qa_cache_questions_vectordb\" using the OpenAI embeddings function and specifies the persist directory as ckpt_dir/curriculum/vectordb. It asserts that the count of items in the collection is equal to the length of the loaded QA cache.", + "type": "comment" + }, + "110": { + "file_id": 11, + "content": " self.qa_cache\n ), (\n f\"Curriculum Agent's qa cache question vectordb is not synced with qa_cache.json.\\n\"\n f\"There are {self.qa_cache_questions_vectordb._collection.count()} questions in vectordb \"\n f\"but {len(self.qa_cache)} questions in qa_cache.json.\\n\"\n f\"Did you set resume=False when initializing the agent?\\n\"\n f\"You may need to manually delete the qa cache question vectordb directory for running from scratch.\\n\"\n )\n # if warm up not defined, initialize it as a dict, else, initialize all the missing value as a default value\n if not warm_up:\n warm_up = self.default_warmup\n self.warm_up = {}\n if \"optional_inventory_items\" in warm_up:\n assert core_inventory_items is not None\n self._core_inv_items_regex = re.compile(core_inventory_items)\n self.warm_up[\"optional_inventory_items\"] = warm_up[\n \"optional_inventory_items\"\n ]\n else:", + "type": "code", + "location": "/voyager/agents/curriculum.py:64-82" + }, + "111": { + "file_id": 11, + "content": "This code is initializing the Curriculum Agent's QA cache, ensuring that the question vector database (vectordb) is synced with qa_cache.json. It checks if 'resume=False' was set during agent initialization and allows for the manual deletion of qa cache questions vectordb directory for running from scratch. The code also initializes the warm-up variable, ensuring it exists or setting missing values to default values.", + "type": "comment" + }, + "112": { + "file_id": 11, + "content": " self.warm_up[\"optional_inventory_items\"] = 0\n for key in self.curriculum_observations:\n self.warm_up[key] = warm_up.get(key, self.default_warmup[key])\n self.warm_up[\"nearby_blocks\"] = 0\n self.warm_up[\"inventory\"] = 0\n self.warm_up[\"completed_tasks\"] = 0\n self.warm_up[\"failed_tasks\"] = 0\n @property\n def default_warmup(self):\n return {\n \"context\": 15,\n \"biome\": 10,\n \"time\": 15,\n \"nearby_blocks\": 0,\n \"other_blocks\": 10,\n \"nearby_entities\": 5,\n \"health\": 15,\n \"hunger\": 15,\n \"position\": 0,\n \"equipment\": 0,\n \"inventory\": 0,\n \"optional_inventory_items\": 7,\n \"chests\": 0,\n \"completed_tasks\": 0,\n \"failed_tasks\": 0,\n }\n @property\n def curriculum_observations(self):\n return [\n \"context\",\n \"biome\",\n \"time\",\n \"nearby_blocks\",\n \"other_blocks\",", + "type": "code", + "location": "/voyager/agents/curriculum.py:83-118" + }, + "113": { + "file_id": 11, + "content": "Sets default warm-up values for curriculum agent, overrides with provided warm-up dictionary.", + "type": "comment" + }, + "114": { + "file_id": 11, + "content": " \"nearby_entities\",\n \"health\",\n \"hunger\",\n \"position\",\n \"equipment\",\n \"inventory\",\n \"chests\",\n \"completed_tasks\",\n \"failed_tasks\",\n ]\n @property\n def progress(self):\n return len(self.completed_tasks)\n def render_system_message(self):\n system_message = SystemMessage(content=load_prompt(\"curriculum\"))\n assert isinstance(system_message, SystemMessage)\n return system_message\n def render_observation(self, *, events, chest_observation):\n assert events[-1][0] == \"observe\", \"Last event must be observe\"\n event = events[-1][1]\n biome = event[\"status\"][\"biome\"]\n time_of_day = event[\"status\"][\"timeOfDay\"]\n voxels = event[\"voxels\"]\n block_records = event[\"blockRecords\"]\n entities = event[\"status\"][\"entities\"]\n health = event[\"status\"][\"health\"]\n hunger = event[\"status\"][\"food\"]\n position = event[\"status\"][\"position\"]", + "type": "code", + "location": "/voyager/agents/curriculum.py:119-149" + }, + "115": { + "file_id": 11, + "content": "This code defines a class with properties for storing information like nearby entities, health, hunger, position, equipment, inventory, chest observations, completed tasks, and failed tasks. It also has methods to calculate progress, render system messages, and generate observations based on events and chest observations, including biome, time of day, voxels, block records, entities' status, and player's health and hunger levels.", + "type": "comment" + }, + "116": { + "file_id": 11, + "content": " equipment = event[\"status\"][\"equipment\"]\n inventory_used = event[\"status\"][\"inventoryUsed\"]\n inventory = event[\"inventory\"]\n if not any(\n \"dirt\" in block\n or \"log\" in block\n or \"grass\" in block\n or \"sand\" in block\n or \"snow\" in block\n for block in voxels\n ):\n biome = \"underground\"\n other_blocks = \", \".join(\n list(\n set(block_records).difference(set(voxels).union(set(inventory.keys())))\n )\n )\n other_blocks = other_blocks if other_blocks else \"None\"\n nearby_entities = (\n \", \".join([k for k, v in sorted(entities.items(), key=lambda x: x[1])])\n if entities\n else \"None\"\n )\n completed_tasks = (\n \", \".join(self.completed_tasks) if self.completed_tasks else \"None\"\n )\n failed_tasks = \", \".join(self.failed_tasks) if self.failed_tasks else \"None\"\n # filter out optional inventory items if required", + "type": "code", + "location": "/voyager/agents/curriculum.py:150-183" + }, + "117": { + "file_id": 11, + "content": "This code block checks the equipment, inventory used, and overall inventory of an event. If there are no dirt, log, grass, sand, or snow blocks in the voxels, it sets the biome as \"underground\". It then identifies other blocks that were not present in the voxels or inventory, lists nearby entities, and lists completed and failed tasks, while handling cases where they might be empty. Finally, it filters out optional inventory items if required.", + "type": "comment" + }, + "118": { + "file_id": 11, + "content": " if self.progress < self.warm_up[\"optional_inventory_items\"]:\n inventory = {\n k: v\n for k, v in inventory.items()\n if self._core_inv_items_regex.search(k) is not None\n }\n observation = {\n \"context\": \"\",\n \"biome\": f\"Biome: {biome}\\n\\n\",\n \"time\": f\"Time: {time_of_day}\\n\\n\",\n \"nearby_blocks\": f\"Nearby blocks: {', '.join(voxels) if voxels else 'None'}\\n\\n\",\n \"other_blocks\": f\"Other blocks that are recently seen: {other_blocks}\\n\\n\",\n \"nearby_entities\": f\"Nearby entities: {nearby_entities}\\n\\n\",\n \"health\": f\"Health: {health:.1f}/20\\n\\n\",\n \"hunger\": f\"Hunger: {hunger:.1f}/20\\n\\n\",\n \"position\": f\"Position: x={position['x']:.1f}, y={position['y']:.1f}, z={position['z']:.1f}\\n\\n\",\n \"equipment\": f\"Equipment: {equipment}\\n\\n\",\n \"inventory\": f\"Inventory ({inventory_used}/36): {inventory if inventory else 'Empty'}\\n\\n\",", + "type": "code", + "location": "/voyager/agents/curriculum.py:184-202" + }, + "119": { + "file_id": 11, + "content": "The code checks if the agent's progress is less than the warm-up optional inventory items. If so, it filters the inventory based on core item regex and prepares an observation dictionary containing various information such as context, biome, time of day, nearby blocks, other blocks, nearby entities, health, hunger, position, equipment, and inventory (filtered). The observation dictionary is formatted with descriptive strings for each field.", + "type": "comment" + }, + "120": { + "file_id": 11, + "content": " \"chests\": chest_observation,\n \"completed_tasks\": f\"Completed tasks so far: {completed_tasks}\\n\\n\",\n \"failed_tasks\": f\"Failed tasks that are too hard: {failed_tasks}\\n\\n\",\n }\n return observation\n def render_human_message(self, *, events, chest_observation):\n content = \"\"\n observation = self.render_observation(\n events=events, chest_observation=chest_observation\n )\n if self.progress >= self.warm_up[\"context\"]:\n questions, answers = self.run_qa(\n events=events, chest_observation=chest_observation\n )\n i = 1\n for question, answer in zip(questions, answers):\n if \"Answer: Unknown\" in answer or \"language model\" in answer:\n continue\n observation[\"context\"] += f\"Question {i}: {question}\\n\"\n observation[\"context\"] += f\"{answer}\\n\\n\"\n i += 1\n if i > 5:\n break\n for key in self.curriculum_observations:", + "type": "code", + "location": "/voyager/agents/curriculum.py:203-228" + }, + "121": { + "file_id": 11, + "content": "This code generates an observation dictionary containing information about completed tasks, failed tasks, and contextual questions with answers. If the progress is past the warm-up phase, it adds up to 5 relevant questions and answers to the observation's \"context\" field.", + "type": "comment" + }, + "122": { + "file_id": 11, + "content": " if self.progress >= self.warm_up[key]:\n if self.warm_up[key] != 0:\n should_include = random.random() < 0.8\n else:\n should_include = True\n if should_include:\n content += observation[key]\n print(f\"\\033[35m****Curriculum Agent human message****\\n{content}\\033[0m\")\n return HumanMessage(content=content)\n def propose_next_task(self, *, events, chest_observation, max_retries=5):\n if self.progress == 0 and self.mode == \"auto\":\n task = \"Mine 1 wood log\"\n context = \"You can mine one of oak, birch, spruce, jungle, acacia, dark oak, or mangrove logs.\"\n return task, context\n # hard code task when inventory is almost full\n inventoryUsed = events[-1][1][\"status\"][\"inventoryUsed\"]\n if inventoryUsed >= 33:\n if chest_observation != \"Chests: None\\n\\n\":\n chests = chest_observation[8:-2].split(\"\\n\")\n for chest in chests:", + "type": "code", + "location": "/voyager/agents/curriculum.py:229-251" + }, + "123": { + "file_id": 11, + "content": "The code defines a Curriculum Agent that generates human messages based on progress, warm-up keys, and observation data. It also proposes tasks depending on progress mode and inventory status. If the progress is zero and mode is \"auto\", it suggests mining one wood log. When inventory is almost full, it checks chest observation to propose tasks related to available chests.", + "type": "comment" + }, + "124": { + "file_id": 11, + "content": " content = chest.split(\":\")[1]\n if content == \" Unknown items inside\" or content == \" Empty\":\n position = chest.split(\":\")[0]\n task = f\"Deposit useless items into the chest at {position}\"\n context = (\n f\"Your inventory have {inventoryUsed} occupied slots before depositing. \"\n \"After depositing, your inventory should only have 20 occupied slots. \"\n \"You should deposit useless items such as andesite, dirt, cobblestone, etc. \"\n \"Also, you can deposit low-level tools, \"\n \"For example, if you have a stone pickaxe, you can deposit a wooden pickaxe. \"\n \"Make sure the list of useless items are in your inventory \"\n \"(do not list items already in the chest), \"\n \"You can use bot.inventoryUsed() to check how many inventory slots are used.\"", + "type": "code", + "location": "/voyager/agents/curriculum.py:252-264" + }, + "125": { + "file_id": 11, + "content": "This code checks the content of a chest and, if it's empty or contains unknown items, creates a task to deposit useless items into it. The user should have 20 occupied slots after depositing, and can use `bot.inventoryUsed()` to check inventory usage.", + "type": "comment" + }, + "126": { + "file_id": 11, + "content": " )\n return task, context\n if \"chest\" in events[-1][1][\"inventory\"]:\n task = \"Place a chest\"\n context = (\n f\"You have a chest in inventory, place it around you. \"\n f\"If chests is not None, or nearby blocks contains chest, this task is success.\"\n )\n else:\n task = \"Craft 1 chest\"\n context = \"Craft 1 chest with 8 planks of any kind of wood.\"\n return task, context\n messages = [\n self.render_system_message(),\n self.render_human_message(\n events=events, chest_observation=chest_observation\n ),\n ]\n if self.mode == \"auto\":\n return self.propose_next_ai_task(messages=messages, max_retries=max_retries)\n elif self.mode == \"manual\":\n return self.propose_next_manual_task()\n else:\n raise ValueError(f\"Invalid curriculum agent mode: {self.mode}\")", + "type": "code", + "location": "/voyager/agents/curriculum.py:265-290" + }, + "127": { + "file_id": 11, + "content": "The code checks if a chest is in the inventory or nearby blocks. If it's present, it sets the task as \"Place a chest\" and provides instructions for placing it around the player. If not, it sets the task as \"Craft 1 chest\" and gives instructions on how to craft one using planks of any wood. The code then generates messages and based on the mode (\"auto\" or \"manual\"), returns the next task proposal or manual task respectively. An error is raised for an invalid mode.", + "type": "comment" + }, + "128": { + "file_id": 11, + "content": " def propose_next_ai_task(self, *, messages, max_retries=5):\n if max_retries == 0:\n raise RuntimeError(\"Max retries reached, failed to propose ai task.\")\n curriculum = self.llm(messages).content\n print(f\"\\033[31m****Curriculum Agent ai message****\\n{curriculum}\\033[0m\")\n try:\n response = self.parse_ai_message(curriculum)\n assert \"next_task\" in response\n context = self.get_task_context(response[\"next_task\"])\n return response[\"next_task\"], context\n except Exception as e:\n print(\n f\"\\033[35mError parsing curriculum response: {e}. Trying again!\\033[0m\"\n )\n return self.propose_next_ai_task(\n messages=messages,\n max_retries=max_retries - 1,\n )\n def parse_ai_message(self, message):\n task = \"\"\n for line in message.split(\"\\n\"):\n if line.startswith(\"Task:\"):\n task = line[5:].replace(\".\", \"\").strip()", + "type": "code", + "location": "/voyager/agents/curriculum.py:292-315" + }, + "129": { + "file_id": 11, + "content": "The code defines a `propose_next_ai_task` method that retrieves the curriculum from AI and parses it to get the next task. If parsing fails, it retries up to max retries or raises an error if max retries is reached. The code also includes a `parse_ai_message` method that extracts the task from the curriculum message.", + "type": "comment" + }, + "130": { + "file_id": 11, + "content": " assert task, \"Task not found in Curriculum Agent response\"\n return {\"next_task\": task}\n def propose_next_manual_task(self):\n confirmed = False\n task, context = \"\", \"\"\n while not confirmed:\n task = input(\"Enter task: \")\n context = input(\"Enter context: \")\n print(f\"Task: {task}\\nContext: {context}\")\n confirmed = input(\"Confirm? (y/n)\").lower() in [\"y\", \"\"]\n return task, context\n def update_exploration_progress(self, info):\n task = info[\"task\"]\n if task.startswith(\"Deposit useless items into the chest at\"):\n # No need to record the deposit task\n return\n if info[\"success\"]:\n print(f\"\\033[35mCompleted task {task}.\\033[0m\")\n self.completed_tasks.append(task)\n else:\n print(\n f\"\\033[35mFailed to complete task {task}. Skipping to next task.\\033[0m\"\n )\n self.failed_tasks.append(task)\n # clean up tasks and dump to disk", + "type": "code", + "location": "/voyager/agents/curriculum.py:316-343" + }, + "131": { + "file_id": 11, + "content": "The code snippet contains three functions: \"get_next_task\", \"propose_next_manual_task\", and \"update_exploration_progress\". The first function retrieves the next task from a response, while the second one prompts the user to input a task and context. Lastly, the third function updates progress by checking task completion and adding tasks to completed or failed lists.", + "type": "comment" + }, + "132": { + "file_id": 11, + "content": " self.clean_up_tasks()\n def clean_up_tasks(self):\n updated_completed_tasks = []\n # record repeated failed tasks\n updated_failed_tasks = self.failed_tasks\n # dedup but keep order\n for task in self.completed_tasks:\n if task not in updated_completed_tasks:\n updated_completed_tasks.append(task)\n # remove completed tasks from failed tasks\n for task in updated_completed_tasks:\n while task in updated_failed_tasks:\n updated_failed_tasks.remove(task)\n self.completed_tasks = updated_completed_tasks\n self.failed_tasks = updated_failed_tasks\n # dump to json\n U.dump_json(\n self.completed_tasks, f\"{self.ckpt_dir}/curriculum/completed_tasks.json\"\n )\n U.dump_json(self.failed_tasks, f\"{self.ckpt_dir}/curriculum/failed_tasks.json\")\n def decompose_task(self, task, events):\n messages = [\n SystemMessage(\n content=load_prompt(\"curriculum_task_decomposition\"),", + "type": "code", + "location": "/voyager/agents/curriculum.py:344-372" + }, + "133": { + "file_id": 11, + "content": "The code snippet is responsible for cleaning up tasks, separating completed and failed ones, storing them in separate lists, and then dumping the updated lists into JSON files. It also contains a function to decompose a task given a set of events and generates a SystemMessage with content loaded from a curriculum_task_decomposition prompt.", + "type": "comment" + }, + "134": { + "file_id": 11, + "content": " ),\n self.render_human_message(events=events, chest_observation=\"\"),\n HumanMessage(content=f\"Final task: {task}\"),\n ]\n print(\n f\"\\033[31m****Curriculum Agent task decomposition****\\nFinal task: {task}\\033[0m\"\n )\n response = self.llm(messages).content\n print(f\"\\033[31m****Curriculum Agent task decomposition****\\n{response}\\033[0m\")\n return fix_and_parse_json(response)\n def run_qa(self, *, events, chest_observation):\n questions_new, _ = self.run_qa_step1_ask_questions(\n events=events, chest_observation=chest_observation\n )\n questions = []\n answers = []\n for question in questions_new:\n if self.qa_cache_questions_vectordb._collection.count() > 0:\n docs_and_scores = (\n self.qa_cache_questions_vectordb.similarity_search_with_score(\n question, k=1\n )\n )\n if docs_and_scores and docs_and_scores[0][1] < 0.05:", + "type": "code", + "location": "/voyager/agents/curriculum.py:373-397" + }, + "135": { + "file_id": 11, + "content": "The code is a part of the Curriculum Agent, which decomposes tasks. It constructs messages for LLM and prints information about the final task. The `run_qa` function retrieves new questions from previous ones, checks if the Vectordb collection has any data, and performs a similarity search to find relevant answers with a score below 0.05.", + "type": "comment" + }, + "136": { + "file_id": 11, + "content": " question_cached = docs_and_scores[0][0].page_content\n assert question_cached in self.qa_cache\n answer_cached = self.qa_cache[question_cached]\n questions.append(question_cached)\n answers.append(answer_cached)\n continue\n answer = self.run_qa_step2_answer_questions(question=question)\n assert question not in self.qa_cache\n self.qa_cache[question] = answer\n self.qa_cache_questions_vectordb.add_texts(\n texts=[question],\n )\n U.dump_json(self.qa_cache, f\"{self.ckpt_dir}/curriculum/qa_cache.json\")\n self.qa_cache_questions_vectordb.persist()\n questions.append(question)\n answers.append(answer)\n assert len(questions_new) == len(questions) == len(answers)\n return questions, answers\n def get_task_context(self, task):\n # if include ore in question, gpt will try to use tool with skill touch enhancement to mine", + "type": "code", + "location": "/voyager/agents/curriculum.py:398-418" + }, + "137": { + "file_id": 11, + "content": "Code snippet:\n- Stores and retrieves question-answer pairs in cache\n- Uses run_qa_step2_answer_questions to generate answers from questions\n- Adds new question to vectordb and persists it\n- Dumps qa_cache into json file at specified location\n- Checks lengths of questions, answers, and questions_new are equal\n- Tool ore in question may prompt GPT to use touch enhancement tool for mining", + "type": "comment" + }, + "138": { + "file_id": 11, + "content": " question = (\n f\"How to {task.replace('_', ' ').replace(' ore', '').replace(' ores', '').replace('.', '').strip().lower()}\"\n f\" in Minecraft?\"\n )\n if question in self.qa_cache:\n answer = self.qa_cache[question]\n else:\n answer = self.run_qa_step2_answer_questions(question=question)\n self.qa_cache[question] = answer\n self.qa_cache_questions_vectordb.add_texts(\n texts=[question],\n )\n U.dump_json(self.qa_cache, f\"{self.ckpt_dir}/curriculum/qa_cache.json\")\n self.qa_cache_questions_vectordb.persist()\n context = f\"Question: {question}\\n{answer}\"\n return context\n def render_system_message_qa_step1_ask_questions(self):\n return SystemMessage(content=load_prompt(\"curriculum_qa_step1_ask_questions\"))\n def render_human_message_qa_step1_ask_questions(self, *, events, chest_observation):\n observation = self.render_observation(\n events=events, chest_observation=chest_observation", + "type": "code", + "location": "/voyager/agents/curriculum.py:419-441" + }, + "139": { + "file_id": 11, + "content": "This code appears to be part of an AI agent that interacts with a user, likely in the context of a game like Minecraft. The code handles questions from the user and answers them using a caching system. It also seems to involve persistence of question-answer pairs for future reference, as well as rendering messages to guide the interaction.", + "type": "comment" + }, + "140": { + "file_id": 11, + "content": " )\n content = \"\"\n for key in self.curriculum_observations:\n content += observation[key]\n return HumanMessage(content=content)\n def run_qa_step1_ask_questions(self, *, events, chest_observation):\n biome = events[-1][1][\"status\"][\"biome\"].replace(\"_\", \" \")\n questions = [\n f\"What are the blocks that I can find in the {biome} in Minecraft?\",\n f\"What are the items that I can find in the {biome} in Minecraft?\",\n f\"What are the mobs that I can find in the {biome} in Minecraft?\",\n ]\n concepts = [biome, biome, biome]\n messages = [\n self.render_system_message_qa_step1_ask_questions(),\n self.render_human_message_qa_step1_ask_questions(\n events=events, chest_observation=chest_observation\n ),\n ]\n qa_response = self.qa_llm(messages).content\n try:\n # Regex pattern to extract question and concept pairs\n pattern = r\"Question \\d+: (.+)\\nConcept \\d+: (.+)\"", + "type": "code", + "location": "/voyager/agents/curriculum.py:442-465" + }, + "141": { + "file_id": 11, + "content": "This code defines two methods: \"render_human_message_qa_step1_ask_questions\" and \"run_qa_step1_ask_questions\". The first method generates a human-readable message from the given events and chest observation. The second method asks three questions related to blocks, items, and mobs in a specific biome in Minecraft using the information from events and chest observation. It then passes these messages to the QA LLM for response, extracts question-concept pairs using regex, and tries to execute it.", + "type": "comment" + }, + "142": { + "file_id": 11, + "content": " # Extracting all question and concept pairs from the text\n pairs = re.findall(pattern, qa_response)\n # Storing each question and concept in separate lists\n questions_new = [pair[0] for pair in pairs]\n concepts_new = [pair[1] for pair in pairs]\n assert len(questions_new) == len(concepts_new)\n questions.extend(questions_new)\n concepts.extend(concepts_new)\n except Exception as e:\n print(\n f\"\\033[35mError parsing curriculum response for \"\n f\"QA step 1 ask questions: {e}.\\033[0m\"\n )\n return questions, concepts\n def render_system_message_qa_step2_answer_questions(self):\n return SystemMessage(\n content=load_prompt(\"curriculum_qa_step2_answer_questions\")\n )\n def render_human_message_qa_step2_answer_questions(self, question):\n content = f\"Question: {question}\"\n return HumanMessage(content=content)\n def run_qa_step2_answer_questions(self, question):", + "type": "code", + "location": "/voyager/agents/curriculum.py:466-490" + }, + "143": { + "file_id": 11, + "content": "The code extracts question and concept pairs from the text, stores them in separate lists, checks their lengths are equal, extends existing question and concept lists, handles exceptions while parsing curriculum response, renders system message with prompt for QA step 2, creates a human message with the question, and runs QA step 2 to answer questions.", + "type": "comment" + }, + "144": { + "file_id": 11, + "content": " messages = [\n self.render_system_message_qa_step2_answer_questions(),\n self.render_human_message_qa_step2_answer_questions(question=question),\n ]\n print(f\"\\033[35mCurriculum Agent Question: {question}\\033[0m\")\n qa_answer = self.qa_llm(messages).content\n print(f\"\\033[31mCurriculum Agent {qa_answer}\\033[0m\")\n return qa_answer", + "type": "code", + "location": "/voyager/agents/curriculum.py:491-498" + }, + "145": { + "file_id": 11, + "content": "This code renders system and human messages for a QA step, prints the curriculum agent's question, asks the QA language model for an answer, and returns it. The question and answer are printed in different colors for clarity.", + "type": "comment" + }, + "146": { + "file_id": 12, + "content": "/voyager/agents/skill.py", + "type": "filepath" + }, + "147": { + "file_id": 12, + "content": "The code initializes a SkillManager class, sets up language model (ChatOpenAI), creates directories for skill storage, loads control primitives, handles checkpoint resuming, manages skills, and utilizes VectorDB for data persistence with conflict resolution. The method retrieves top K skills based on query using vector database similarity search and returns their codes.", + "type": "summary" + }, + "148": { + "file_id": 12, + "content": "import os\nimport voyager.utils as U\nfrom langchain.chat_models import ChatOpenAI\nfrom langchain.embeddings.openai import OpenAIEmbeddings\nfrom langchain.schema import HumanMessage, SystemMessage\nfrom langchain.vectorstores import Chroma\nfrom voyager.prompts import load_prompt\nfrom voyager.control_primitives import load_control_primitives\nclass SkillManager:\n def __init__(\n self,\n model_name=\"gpt-3.5-turbo\",\n temperature=0,\n retrieval_top_k=5,\n request_timout=120,\n ckpt_dir=\"ckpt\",\n resume=False,\n ):\n self.llm = ChatOpenAI(\n model_name=model_name,\n temperature=temperature,\n request_timeout=request_timout,\n )\n U.f_mkdir(f\"{ckpt_dir}/skill/code\")\n U.f_mkdir(f\"{ckpt_dir}/skill/description\")\n U.f_mkdir(f\"{ckpt_dir}/skill/vectordb\")\n # programs for env execution\n self.control_primitives = load_control_primitives()\n if resume:\n print(f\"\\033[33mLoading Skill Manager from {ckpt_dir}/skill\\033[0m\")", + "type": "code", + "location": "/voyager/agents/skill.py:1-34" + }, + "149": { + "file_id": 12, + "content": "The code initializes a SkillManager class, sets up the language model (ChatOpenAI), creates directories for skill storage, loads control primitives, and handles resuming from previous checkpoints if necessary.", + "type": "comment" + }, + "150": { + "file_id": 12, + "content": " self.skills = U.load_json(f\"{ckpt_dir}/skill/skills.json\")\n else:\n self.skills = {}\n self.retrieval_top_k = retrieval_top_k\n self.ckpt_dir = ckpt_dir\n self.vectordb = Chroma(\n collection_name=\"skill_vectordb\",\n embedding_function=OpenAIEmbeddings(),\n persist_directory=f\"{ckpt_dir}/skill/vectordb\",\n )\n assert self.vectordb._collection.count() == len(self.skills), (\n f\"Skill Manager's vectordb is not synced with skills.json.\\n\"\n f\"There are {self.vectordb._collection.count()} skills in vectordb but {len(self.skills)} skills in skills.json.\\n\"\n f\"Did you set resume=False when initializing the manager?\\n\"\n f\"You may need to manually delete the vectordb directory for running from scratch.\"\n )\n @property\n def programs(self):\n programs = \"\"\n for skill_name, entry in self.skills.items():\n programs += f\"{entry['code']}\\n\\n\"\n for primitives in self.control_primitives:", + "type": "code", + "location": "/voyager/agents/skill.py:35-57" + }, + "151": { + "file_id": 12, + "content": "The code initializes a SkillManager instance with skills loaded from a JSON file or an empty dictionary if none exists, sets retrieval_top_k and ckpt_dir attributes, creates a Chroma VectorDB with OpenAIEmbeddings, and asserts that the number of skills in the VectorDB matches the length of the skills.json file. It also provides a program property that returns programs from the skills.json file and control primitives.", + "type": "comment" + }, + "152": { + "file_id": 12, + "content": " programs += f\"{primitives}\\n\\n\"\n return programs\n def add_new_skill(self, info):\n if info[\"task\"].startswith(\"Deposit useless items into the chest at\"):\n # No need to reuse the deposit skill\n return\n program_name = info[\"program_name\"]\n program_code = info[\"program_code\"]\n skill_description = self.generate_skill_description(program_name, program_code)\n print(\n f\"\\033[33mSkill Manager generated description for {program_name}:\\n{skill_description}\\033[0m\"\n )\n if program_name in self.skills:\n print(f\"\\033[33mSkill {program_name} already exists. Rewriting!\\033[0m\")\n self.vectordb._collection.delete(ids=[program_name])\n i = 2\n while f\"{program_name}V{i}.js\" in os.listdir(f\"{self.ckpt_dir}/skill/code\"):\n i += 1\n dumped_program_name = f\"{program_name}V{i}\"\n else:\n dumped_program_name = program_name\n self.vectordb.add_texts(", + "type": "code", + "location": "/voyager/agents/skill.py:58-80" + }, + "153": { + "file_id": 12, + "content": "The code defines two functions: \"get_programs\" and \"add_new_skill\". The get_programs function generates a list of skills based on primitives, appends them to the programs variable with newline separators, and returns the final list. The add_new_skill function checks if a skill with the given name and code already exists. If it does, it deletes the existing skill and renames the current one to avoid conflicts before adding it to the database.", + "type": "comment" + }, + "154": { + "file_id": 12, + "content": " texts=[skill_description],\n ids=[program_name],\n metadatas=[{\"name\": program_name}],\n )\n self.skills[program_name] = {\n \"code\": program_code,\n \"description\": skill_description,\n }\n assert self.vectordb._collection.count() == len(\n self.skills\n ), \"vectordb is not synced with skills.json\"\n U.dump_text(\n program_code, f\"{self.ckpt_dir}/skill/code/{dumped_program_name}.js\"\n )\n U.dump_text(\n skill_description,\n f\"{self.ckpt_dir}/skill/description/{dumped_program_name}.txt\",\n )\n U.dump_json(self.skills, f\"{self.ckpt_dir}/skill/skills.json\")\n self.vectordb.persist()\n def generate_skill_description(self, program_name, program_code):\n messages = [\n SystemMessage(content=load_prompt(\"skill\")),\n HumanMessage(\n content=program_code\n + \"\\n\\n\"\n + f\"The main function is `{program_name}`.\"", + "type": "code", + "location": "/voyager/agents/skill.py:81-108" + }, + "155": { + "file_id": 12, + "content": "This code is responsible for managing skills in a program, creating the necessary files and folders to store skill information such as code, description, and metadata. It uses VectorDB to persist data and U.dump functions to write text and JSON files. The `generate_skill_description` function generates a description based on the provided program name and code by passing it through the LLM with a system message loaded from \"load_prompt('skill')\". This function is used in skill creation or editing scenarios.", + "type": "comment" + }, + "156": { + "file_id": 12, + "content": " ),\n ]\n skill_description = f\" // { self.llm(messages).content}\"\n return f\"async function {program_name}(bot) {{\\n{skill_description}\\n}}\"\n def retrieve_skills(self, query):\n k = min(self.vectordb._collection.count(), self.retrieval_top_k)\n if k == 0:\n return []\n print(f\"\\033[33mSkill Manager retrieving for {k} skills\\033[0m\")\n docs_and_scores = self.vectordb.similarity_search_with_score(query, k=k)\n print(\n f\"\\033[33mSkill Manager retrieved skills: \"\n f\"{', '.join([doc.metadata['name'] for doc, _ in docs_and_scores])}\\033[0m\"\n )\n skills = []\n for doc, _ in docs_and_scores:\n skills.append(self.skills[doc.metadata[\"name\"]][\"code\"])\n return skills", + "type": "code", + "location": "/voyager/agents/skill.py:109-127" + }, + "157": { + "file_id": 12, + "content": "This code snippet defines a method in the Voyager/voyager/agents/skill.py file responsible for retrieving skills based on query. It uses vector database similarity search to find top K skills and returns their corresponding codes. The process involves printing status messages during skill retrieval, with potential error handling for cases where no results are found or the retrieved skills' names are not present in the skills dictionary.", + "type": "comment" + }, + "158": { + "file_id": 13, + "content": "/voyager/control_primitives/__init__.py", + "type": "filepath" + }, + "159": { + "file_id": 13, + "content": "This function, load_control_primitives, imports necessary packages and defines the path to the \"voyager\" package. If no primitive names are provided, it retrieves them from the \"control_primitives\" directory within the package. It then loads these primitives as text files into a list and returns them.", + "type": "summary" + }, + "160": { + "file_id": 13, + "content": "import pkg_resources\nimport os\nimport voyager.utils as U\ndef load_control_primitives(primitive_names=None):\n package_path = pkg_resources.resource_filename(\"voyager\", \"\")\n if primitive_names is None:\n primitive_names = [\n primitives[:-3]\n for primitives in os.listdir(f\"{package_path}/control_primitives\")\n if primitives.endswith(\".js\")\n ]\n primitives = [\n U.load_text(f\"{package_path}/control_primitives/{primitive_name}.js\")\n for primitive_name in primitive_names\n ]\n return primitives", + "type": "code", + "location": "/voyager/control_primitives/__init__.py:1-18" + }, + "161": { + "file_id": 13, + "content": "This function, load_control_primitives, imports necessary packages and defines the path to the \"voyager\" package. If no primitive names are provided, it retrieves them from the \"control_primitives\" directory within the package. It then loads these primitives as text files into a list and returns them.", + "type": "comment" + }, + "162": { + "file_id": 14, + "content": "/voyager/control_primitives/craftHelper.js", + "type": "filepath" + }, + "163": { + "file_id": 14, + "content": "The function finds the minimum recipe for an item by checking nearby crafting tables and informs users about missing items needed to craft.", + "type": "summary" + }, + "164": { + "file_id": 14, + "content": "function failedCraftFeedback(bot, name, item, craftingTable) {\n const recipes = bot.recipesAll(item.id, null, craftingTable);\n if (!recipes.length) {\n throw new Error(`No crafting table nearby`);\n } else {\n const recipes = bot.recipesAll(\n item.id,\n null,\n mcData.blocksByName.crafting_table.id\n );\n // find the recipe with the fewest missing ingredients\n var min = 999;\n var min_recipe = null;\n for (const recipe of recipes) {\n const delta = recipe.delta;\n var missing = 0;\n for (const delta_item of delta) {\n if (delta_item.count < 0) {\n const inventory_item = bot.inventory.findInventoryItem(\n mcData.items[delta_item.id].name,\n null\n );\n if (!inventory_item) {\n missing += -delta_item.count;\n } else {\n missing += Math.max(", + "type": "code", + "location": "/voyager/control_primitives/craftHelper.js:1-26" + }, + "165": { + "file_id": 14, + "content": "The function checks if there's a crafting table nearby and finds the recipe with the least missing ingredients for a specific item. It throws an error if no crafting table is found, and iterates through recipes to determine the number of missing ingredients.", + "type": "comment" + }, + "166": { + "file_id": 14, + "content": " -delta_item.count - inventory_item.count,\n 0\n );\n }\n }\n }\n if (missing < min) {\n min = missing;\n min_recipe = recipe;\n }\n }\n const delta = min_recipe.delta;\n let message = \"\";\n for (const delta_item of delta) {\n if (delta_item.count < 0) {\n const inventory_item = bot.inventory.findInventoryItem(\n mcData.items[delta_item.id].name,\n null\n );\n if (!inventory_item) {\n message += ` ${-delta_item.count} more ${\n mcData.items[delta_item.id].name\n }, `;\n } else {\n if (inventory_item.count < -delta_item.count) {\n message += `${\n -delta_item.count - inventory_item.count\n } more ${mcData.items[delta_item.id].name}`;", + "type": "code", + "location": "/voyager/control_primitives/craftHelper.js:27-54" + }, + "167": { + "file_id": 14, + "content": "This code checks if a recipe is missing any items, calculates the minimum missing item count for each ingredient, and stores the corresponding recipe. Then, it iterates through the delta (missing item counts) of each ingredient and checks if any are negative. If an ingredient's count is negative in the bot's inventory, it adds the absolute value of that difference to the message string. If the count is less than the missing count, it updates the minimum missing count and recipe accordingly. Finally, it assigns the minimum recipe delta to the variable 'delta'.", + "type": "comment" + }, + "168": { + "file_id": 14, + "content": " }\n }\n }\n }\n bot.chat(`I cannot make ${name} because I need: ${message}`);\n }\n}", + "type": "code", + "location": "/voyager/control_primitives/craftHelper.js:55-61" + }, + "169": { + "file_id": 14, + "content": "The code checks if the bot has the required items to craft a specified item. If it doesn't, it informs the user about the missing items.", + "type": "comment" + }, + "170": { + "file_id": 15, + "content": "/voyager/control_primitives/craftItem.js", + "type": "filepath" + }, + "171": { + "file_id": 15, + "content": "\"craftItem\" is an asynchronous function that crafts items using a crafting table, validating inputs and catching errors with feedback before throwing an error after 10 tries.", + "type": "summary" + }, + "172": { + "file_id": 15, + "content": "async function craftItem(bot, name, count = 1) {\n // return if name is not string\n if (typeof name !== \"string\") {\n throw new Error(\"name for craftItem must be a string\");\n }\n // return if count is not number\n if (typeof count !== \"number\") {\n throw new Error(\"count for craftItem must be a number\");\n }\n const itemByName = mcData.itemsByName[name];\n if (!itemByName) {\n throw new Error(`No item named ${name}`);\n }\n const craftingTable = bot.findBlock({\n matching: mcData.blocksByName.crafting_table.id,\n maxDistance: 32,\n });\n if (!craftingTable) {\n bot.chat(\"Craft without a crafting table\");\n } else {\n await bot.pathfinder.goto(\n new GoalLookAtBlock(craftingTable.position, bot.world)\n );\n }\n const recipe = bot.recipesFor(itemByName.id, null, 1, craftingTable)[0];\n if (recipe) {\n bot.chat(`I can make ${name}`);\n try {\n await bot.craft(recipe, count, craftingTable);\n bot.chat(`I did the recipe for ${name} ${count} times`);", + "type": "code", + "location": "/voyager/control_primitives/craftItem.js:1-30" + }, + "173": { + "file_id": 15, + "content": "This code defines an asynchronous function named \"craftItem\" that crafts a specified item by finding and using a crafting table. It checks if the input name is a string and count is a number before executing, ensuring valid parameters. If valid, it finds the nearest crafting table and attempts to craft the item using the bot's \"craft\" function.", + "type": "comment" + }, + "174": { + "file_id": 15, + "content": " } catch (err) {\n bot.chat(`I cannot do the recipe for ${name} ${count} times`);\n }\n } else {\n failedCraftFeedback(bot, name, itemByName, craftingTable);\n _craftItemFailCount++;\n if (_craftItemFailCount > 10) {\n throw new Error(\n \"craftItem failed too many times, check chat log to see what happened\"\n );\n }\n }\n}", + "type": "code", + "location": "/voyager/control_primitives/craftItem.js:31-43" + }, + "175": { + "file_id": 15, + "content": "Code block catches errors in crafting recipes and provides feedback for failed attempts, limiting the maximum number of tries to 10 before throwing an error.", + "type": "comment" + }, + "176": { + "file_id": 16, + "content": "/voyager/control_primitives/exploreUntil.js", + "type": "filepath" + }, + "177": { + "file_id": 16, + "content": "The `exploreUntil` function executes exploration tasks with a maximum time limit, resolving the result or null based on success or timeout.", + "type": "summary" + }, + "178": { + "file_id": 16, + "content": "// Explore downward for 60 seconds: exploreUntil(bot, new Vec3(0, -1, 0), 60);\nasync function exploreUntil(\n bot,\n direction,\n maxTime = 60,\n callback = () => {\n return false;\n }\n) {\n if (typeof maxTime !== \"number\") {\n throw new Error(\"maxTime must be a number\");\n }\n if (typeof callback !== \"function\") {\n throw new Error(\"callback must be a function\");\n }\n const test = callback();\n if (test) {\n bot.chat(\"Explore success.\");\n return Promise.resolve(test);\n }\n if (direction.x === 0 && direction.y === 0 && direction.z === 0) {\n throw new Error(\"direction cannot be 0, 0, 0\");\n }\n if (\n !(\n (direction.x === 0 || direction.x === 1 || direction.x === -1) &&\n (direction.y === 0 || direction.y === 1 || direction.y === -1) &&\n (direction.z === 0 || direction.z === 1 || direction.z === -1)\n )\n ) {\n throw new Error(\n \"direction must be a Vec3 only with value of -1, 0 or 1\"\n );", + "type": "code", + "location": "/voyager/control_primitives/exploreUntil.js:1-33" + }, + "179": { + "file_id": 16, + "content": "Explore downward for 60 seconds: exploreUntil(bot, new Vec3(0, -1, 0), 60);\n\nThe function `exploreUntil` takes a bot, direction, maximum time (default 60), and a callback. It checks if maxTime is a number, and if the callback is a function. If successful, it returns a resolved promise with the test result. If direction is 0 in all dimensions, it throws an error. If direction values are not -1, 0, or 1, it also throws an error.", + "type": "comment" + }, + "180": { + "file_id": 16, + "content": " }\n maxTime = Math.min(maxTime, 1200);\n return new Promise((resolve, reject) => {\n const dx = direction.x;\n const dy = direction.y;\n const dz = direction.z;\n let explorationInterval;\n let maxTimeTimeout;\n const cleanUp = () => {\n clearInterval(explorationInterval);\n clearTimeout(maxTimeTimeout);\n bot.pathfinder.setGoal(null);\n };\n const explore = () => {\n const x =\n bot.entity.position.x +\n Math.floor(Math.random() * 20 + 10) * dx;\n const y =\n bot.entity.position.y +\n Math.floor(Math.random() * 20 + 10) * dy;\n const z =\n bot.entity.position.z +\n Math.floor(Math.random() * 20 + 10) * dz;\n let goal = new GoalNear(x, y, z);\n if (dy === 0) {\n goal = new GoalNearXZ(x, z);\n }\n bot.pathfinder.setGoal(goal);\n try {\n const result = callback();", + "type": "code", + "location": "/voyager/control_primitives/exploreUntil.js:34-67" + }, + "181": { + "file_id": 16, + "content": "The code sets up an interval-based exploration function that randomly generates new exploration goals within a 20x20x20 block around the current entity position. The maxTime parameter limits the total time spent exploring to either the specified value or 1200 seconds. The function returns a Promise, and includes cleanup functions for clearing the interval and goal when complete or timed out.", + "type": "comment" + }, + "182": { + "file_id": 16, + "content": " if (result) {\n cleanUp();\n bot.chat(\"Explore success.\");\n resolve(result);\n }\n } catch (err) {\n cleanUp();\n reject(err);\n }\n };\n explorationInterval = setInterval(explore, 2000);\n maxTimeTimeout = setTimeout(() => {\n cleanUp();\n bot.chat(\"Max exploration time reached\");\n resolve(null);\n }, maxTime * 1000);\n });\n}", + "type": "code", + "location": "/voyager/control_primitives/exploreUntil.js:68-87" + }, + "183": { + "file_id": 16, + "content": "The code performs an exploration task with a maximum time limit. If the exploration succeeds within the specified time, it cleans up and resolves the result. Otherwise, it cleans up, rejects the error, and resolves null. The code sets two intervals: one for exploration at 2-second intervals, and another for timeout after the maximum time in milliseconds.", + "type": "comment" + }, + "184": { + "file_id": 17, + "content": "/voyager/control_primitives/givePlacedItemBack.js", + "type": "filepath" + }, + "185": { + "file_id": 17, + "content": "The givePlacedItemBack function, using parameters bot, name, and position, disables doTileDrops, calls givePlacedItemBackSingle for each pair of name and position, enables doTileDrops again. The givePlacedItemBackSingle gives item to the bot and updates history with block positions within a certain radius. This code destroys blocks, updates history, waits 20 ticks, and returns.", + "type": "summary" + }, + "186": { + "file_id": 17, + "content": "async function givePlacedItemBack(bot, name, position) {\n await bot.chat(\"/gamerule doTileDrops false\");\n // iterate name and position\n const history = [];\n for (let i = 0; i < name.length; i++) {\n await givePlacedItemBackSingle(bot, name[i], position[i]);\n }\n await bot.chat(\"/gamerule doTileDrops true\");\n async function givePlacedItemBackSingle(bot, name, position) {\n bot.chat(`/give bot ${name} 1`);\n const x = Math.floor(position.x);\n const y = Math.floor(position.y);\n const z = Math.floor(position.z);\n // loop through 125 blocks around the block\n const size = 3;\n for (let dx = -size; dx <= size; dx++) {\n for (let dy = -size; dy <= size; dy++) {\n for (let dz = -size; dz <= size; dz++) {\n const block = bot.blockAt(new Vec3(x + dx, y + dy, z + dz));\n if (\n block?.name === name &&\n !history.includes(block.position)\n ) {", + "type": "code", + "location": "/voyager/control_primitives/givePlacedItemBack.js:1-24" + }, + "187": { + "file_id": 17, + "content": "Function givePlacedItemBack takes in a bot, name, and position as parameters. It disables doTileDrops, iterates through each pair of name and position, calls givePlacedItemBackSingle for each pair, then enables doTileDrops again. The givePlacedItemBackSingle function gives the item to the bot and checks if any blocks with the same name are within a certain radius, adding their positions to history if not already included.", + "type": "comment" + }, + "188": { + "file_id": 17, + "content": " await bot.chat(\n `/setblock ${x + dx} ${y + dy} ${\n z + dz\n } air destroy`\n );\n history.push(block.position);\n await bot.waitForTicks(20);\n return;\n }\n }\n }\n }\n }\n}", + "type": "code", + "location": "/voyager/control_primitives/givePlacedItemBack.js:25-38" + }, + "189": { + "file_id": 17, + "content": "This code sets a block at a specific location to air and destroys it if the condition is met, updates the history with the block's position, waits for 20 ticks, and returns.", + "type": "comment" + }, + "190": { + "file_id": 18, + "content": "/voyager/control_primitives/killMob.js", + "type": "filepath" + }, + "191": { + "file_id": 18, + "content": "This function checks inputs and retrieves the nearest entity with given name, throwing an error if not found. It handles mob killing and item collection in a game using auto-attack or regular attack based on equipped weapon, waiting for the mob to be removed before collecting items. If successful, progress is saved as \"${mobName}_killed\".", + "type": "summary" + }, + "192": { + "file_id": 18, + "content": "async function killMob(bot, mobName, timeout = 300) {\n // return if mobName is not string\n if (typeof mobName !== \"string\") {\n throw new Error(`mobName for killMob must be a string`);\n }\n // return if timeout is not number\n if (typeof timeout !== \"number\") {\n throw new Error(`timeout for killMob must be a number`);\n }\n const weaponsForShooting = [\n \"bow\",\n \"crossbow\",\n \"snowball\",\n \"ender_pearl\",\n \"egg\",\n \"splash_potion\",\n \"trident\",\n ];\n const mainHandItem = bot.inventory.slots[bot.getEquipmentDestSlot(\"hand\")];\n const entity = bot.nearestEntity(\n (entity) =>\n entity.name === mobName &&\n // kill mob distance should be slightly bigger than explore distance\n entity.position.distanceTo(bot.entity.position) < 48\n );\n if (!entity) {\n bot.chat(`No ${mobName} nearby, please explore first`);\n _killMobFailCount++;\n if (_killMobFailCount > 10) {\n throw new Error(", + "type": "code", + "location": "/voyager/control_primitives/killMob.js:1-32" + }, + "193": { + "file_id": 18, + "content": "This function takes a bot, mob name and timeout as inputs. It checks if both the mobName is a string and timeout is a number. If not, it throws an error. Then, it retrieves the nearest entity with the given mobName, ensuring it's within a certain distance. If no entity is found, it sends a chat message and increments the fail count; if the fail count exceeds 10, it throws an error.", + "type": "comment" + }, + "194": { + "file_id": 18, + "content": " `killMob failed too many times, make sure you explore before calling killMob`\n );\n }\n return;\n }\n let droppedItem;\n if (mainHandItem && weaponsForShooting.includes(mainHandItem.name)) {\n bot.hawkEye.autoAttack(entity, mainHandItem.name);\n droppedItem = await waitForMobShot(bot, entity, timeout);\n } else {\n await bot.pvp.attack(entity);\n droppedItem = await waitForMobRemoved(bot, entity, timeout);\n }\n if (droppedItem) {\n await bot.collectBlock.collect(droppedItem, { ignoreNoPath: true });\n }\n bot.save(`${mobName}_killed`);\n}", + "type": "code", + "location": "/voyager/control_primitives/killMob.js:33-51" + }, + "195": { + "file_id": 18, + "content": "This code handles the killing of a mob and subsequent item collection in a game. It uses auto-attack or regular attack based on the weapon equipped, then waits for the mob to be removed before collecting any dropped items. If successful, it saves the progress as \"${mobName}_killed\".", + "type": "comment" + }, + "196": { + "file_id": 19, + "content": "/voyager/control_primitives/mineBlock.js", + "type": "filepath" + }, + "197": { + "file_id": 19, + "content": "This function verifies valid inputs and collects blocks using the bot's `collectBlock` method. It initializes a mine block operation, ignores path, specifies count, saves bot state as `${name}_mined`, and notifies user if errors occur or no blocks are found.", + "type": "summary" + }, + "198": { + "file_id": 19, + "content": "async function mineBlock(bot, name, count = 1) {\n // return if name is not string\n if (typeof name !== \"string\") {\n throw new Error(`name for mineBlock must be a string`);\n }\n if (typeof count !== \"number\") {\n throw new Error(`count for mineBlock must be a number`);\n }\n const blockByName = mcData.blocksByName[name];\n if (!blockByName) {\n throw new Error(`No block named ${name}`);\n }\n const blocks = bot.findBlocks({\n matching: [blockByName.id],\n maxDistance: 32,\n count: 1024,\n });\n if (blocks.length === 0) {\n bot.chat(`No ${name} nearby, please explore first`);\n _mineBlockFailCount++;\n if (_mineBlockFailCount > 10) {\n throw new Error(\n \"mineBlock failed too many times, make sure you explore before calling mineBlock\"\n );\n }\n return;\n }\n const targets = [];\n for (let i = 0; i < blocks.length; i++) {\n targets.push(bot.blockAt(blocks[i]));\n }\n await bot.collectBlock.collect(targets, {", + "type": "code", + "location": "/voyager/control_primitives/mineBlock.js:1-32" + }, + "199": { + "file_id": 19, + "content": "This function checks if the provided name and count are valid string and number respectively. It then searches for blocks with the specified name, and if found, collects them using the bot's `collectBlock` method. If no blocks are found or an error occurs during collection, it notifies the user accordingly.", + "type": "comment" + } +} \ No newline at end of file diff --git a/docs/data/2.json b/docs/data/2.json new file mode 100644 index 00000000..7f1e8d41 --- /dev/null +++ b/docs/data/2.json @@ -0,0 +1,536 @@ +{ + "200": { + "file_id": 19, + "content": " ignoreNoPath: true,\n count: count,\n });\n bot.save(`${name}_mined`);\n}", + "type": "code", + "location": "/voyager/control_primitives/mineBlock.js:33-37" + }, + "201": { + "file_id": 19, + "content": "The code snippet initializes a mine block operation, ignoring any absence of path and specifying the count. After completion, it saves the bot's state as `${name}_mined`.", + "type": "comment" + }, + "202": { + "file_id": 20, + "content": "/voyager/control_primitives/placeItem.js", + "type": "filepath" + }, + "203": { + "file_id": 20, + "content": "The function placeItem verifies item existence, position, and equipment before placement. It handles inventory count mismatches, saves upon success, and reports errors.", + "type": "summary" + }, + "204": { + "file_id": 20, + "content": "async function placeItem(bot, name, position) {\n // return if name is not string\n if (typeof name !== \"string\") {\n throw new Error(`name for placeItem must be a string`);\n }\n // return if position is not Vec3\n if (!(position instanceof Vec3)) {\n throw new Error(`position for placeItem must be a Vec3`);\n }\n const itemByName = mcData.itemsByName[name];\n if (!itemByName) {\n throw new Error(`No item named ${name}`);\n }\n const item = bot.inventory.findInventoryItem(itemByName.id);\n if (!item) {\n bot.chat(`No ${name} in inventory`);\n return;\n }\n const item_count = item.count;\n // find a reference block\n const faceVectors = [\n new Vec3(0, 1, 0),\n new Vec3(0, -1, 0),\n new Vec3(1, 0, 0),\n new Vec3(-1, 0, 0),\n new Vec3(0, 0, 1),\n new Vec3(0, 0, -1),\n ];\n let referenceBlock = null;\n let faceVector = null;\n for (const vector of faceVectors) {\n const block = bot.blockAt(position.minus(vector));", + "type": "code", + "location": "/voyager/control_primitives/placeItem.js:1-32" + }, + "205": { + "file_id": 20, + "content": "This function, placeItem, accepts three parameters: bot, name, and position. It checks if the name is a string and the position is a Vec3. If not, it throws an error. The function then verifies that there is an item with the given name and checks if it exists in the bot's inventory. If not, it notifies via chat and returns. If everything is fine, it searches for a reference block based on faceVectors, iterating through six possible vectors to find a suitable position to place the item.", + "type": "comment" + }, + "206": { + "file_id": 20, + "content": " if (block?.name !== \"air\") {\n referenceBlock = block;\n faceVector = vector;\n bot.chat(`Placing ${name} on ${block.name} at ${block.position}`);\n break;\n }\n }\n if (!referenceBlock) {\n bot.chat(\n `No block to place ${name} on. You cannot place a floating block.`\n );\n _placeItemFailCount++;\n if (_placeItemFailCount > 10) {\n throw new Error(\n `placeItem failed too many times. You cannot place a floating block.`\n );\n }\n return;\n }\n // You must use try catch to placeBlock\n try {\n // You must first go to the block position you want to place\n await bot.pathfinder.goto(new GoalPlaceBlock(position, bot.world, {}));\n // You must equip the item right before calling placeBlock\n await bot.equip(item, \"hand\");\n await bot.placeBlock(referenceBlock, faceVector);\n bot.chat(`Placed ${name}`);\n bot.save(`${name}_placed`);\n } catch (err) {", + "type": "code", + "location": "/voyager/control_primitives/placeItem.js:33-62" + }, + "207": { + "file_id": 20, + "content": "This code checks if there's a block to place an item on, then goes to the target block position, equips the item, and places it. If no block is found or placing fails too many times, it reports an error.", + "type": "comment" + }, + "208": { + "file_id": 20, + "content": " const item = bot.inventory.findInventoryItem(itemByName.id);\n if (item?.count === item_count) {\n bot.chat(\n `Error placing ${name}: ${err.message}, please find another position to place`\n );\n _placeItemFailCount++;\n if (_placeItemFailCount > 10) {\n throw new Error(\n `placeItem failed too many times, please find another position to place.`\n );\n }\n } else {\n bot.chat(`Placed ${name}`);\n bot.save(`${name}_placed`);\n }\n }\n}", + "type": "code", + "location": "/voyager/control_primitives/placeItem.js:63-79" + }, + "209": { + "file_id": 20, + "content": "Checks if the item count in inventory matches, handles failure and displays error message, and saves when successfully placed.", + "type": "comment" + }, + "210": { + "file_id": 21, + "content": "/voyager/control_primitives/shoot.js", + "type": "filepath" + }, + "211": { + "file_id": 21, + "content": "This function \"shoot\" allows a bot to shoot a valid weapon at a target entity. It checks if the weapon is valid, if it exists in the bot's inventory, and if the target is nearby. If all conditions are met, it auto-attacks using a bow and handles auto_shot_stopped event.", + "type": "summary" + }, + "212": { + "file_id": 21, + "content": "// shoot 1 pig with a bow: shoot(bot, \"bow\", \"pig\");\nasync function shoot(bot, weapon, target) {\n const validWeapons = [\n \"bow\",\n \"crossbow\",\n \"snowball\",\n \"ender_pearl\",\n \"egg\",\n \"splash_potion\",\n \"trident\",\n ];\n if (!validWeapons.includes(weapon)) {\n bot.chat(`${weapon} is not a valid weapon for shooting`);\n return;\n }\n const weaponItem = mcData.itemsByName[weapon];\n if (!bot.inventory.findInventoryItem(weaponItem.id, null)) {\n bot.chat(`No ${weapon} in inventory for shooting`);\n return;\n }\n const targetEntity = bot.nearestEntity(\n (entity) =>\n entity.name === target\n );\n if (!targetEntity) {\n bot.chat(`No ${target} nearby`);\n return;\n }\n bot.hawkEye.autoAttack(targetEntity, \"bow\");\n bot.on('auto_shot_stopped', (target) => {\n })\n}", + "type": "code", + "location": "/voyager/control_primitives/shoot.js:1-34" + }, + "213": { + "file_id": 21, + "content": "This function \"shoot\" allows a bot to shoot a valid weapon at a target entity. It checks if the weapon is valid, if it exists in the bot's inventory, and if the target is nearby. If all conditions are met, it auto-attacks using a bow and handles auto_shot_stopped event.", + "type": "comment" + }, + "214": { + "file_id": 22, + "content": "/voyager/control_primitives/smeltItem.js", + "type": "filepath" + }, + "215": { + "file_id": 22, + "content": "This function enables a bot to smelt items using a furnace, managing inventory and pathfinding, handles errors, and notifies users upon failure.", + "type": "summary" + }, + "216": { + "file_id": 22, + "content": "async function smeltItem(bot, itemName, fuelName, count = 1) {\n // return if itemName or fuelName is not string\n if (typeof itemName !== \"string\" || typeof fuelName !== \"string\") {\n throw new Error(\"itemName or fuelName for smeltItem must be a string\");\n }\n // return if count is not a number\n if (typeof count !== \"number\") {\n throw new Error(\"count for smeltItem must be a number\");\n }\n const item = mcData.itemsByName[itemName];\n const fuel = mcData.itemsByName[fuelName];\n if (!item) {\n throw new Error(`No item named ${itemName}`);\n }\n if (!fuel) {\n throw new Error(`No item named ${fuelName}`);\n }\n const furnaceBlock = bot.findBlock({\n matching: mcData.blocksByName.furnace.id,\n maxDistance: 32,\n });\n if (!furnaceBlock) {\n throw new Error(\"No furnace nearby\");\n } else {\n await bot.pathfinder.goto(\n new GoalLookAtBlock(furnaceBlock.position, bot.world)\n );\n }\n const furnace = await bot.openFurnace(furnaceBlock);", + "type": "code", + "location": "/voyager/control_primitives/smeltItem.js:1-29" + }, + "217": { + "file_id": 22, + "content": "Function to smelt an item using a furnace. Checks if itemName and fuelName are strings, and count is a number. Verifies the existence of items and a nearby furnace. Pathfinding to the furnace and opening it for further processing.", + "type": "comment" + }, + "218": { + "file_id": 22, + "content": " let success_count = 0;\n for (let i = 0; i < count; i++) {\n if (!bot.inventory.findInventoryItem(item.id, null)) {\n bot.chat(`No ${itemName} to smelt in inventory`);\n break;\n }\n if (furnace.fuelSeconds < 15 && furnace.fuelItem()?.name !== fuelName) {\n if (!bot.inventory.findInventoryItem(fuel.id, null)) {\n bot.chat(`No ${fuelName} as fuel in inventory`);\n break;\n }\n await furnace.putFuel(fuel.id, null, 1);\n await bot.waitForTicks(20);\n if (!furnace.fuel && furnace.fuelItem()?.name !== fuelName) {\n throw new Error(`${fuelName} is not a valid fuel`);\n }\n }\n await furnace.putInput(item.id, null, 1);\n await bot.waitForTicks(12 * 20);\n if (!furnace.outputItem()) {\n throw new Error(`${itemName} is not a valid input`);\n }\n await furnace.takeOutput();\n success_count++;\n }\n furnace.close();\n if (success_count > 0) bot.chat(`Smelted ${success_count} ${itemName}.`);", + "type": "code", + "location": "/voyager/control_primitives/smeltItem.js:30-56" + }, + "219": { + "file_id": 22, + "content": "The code checks if the bot has the required items (item and fuel) in its inventory, then smelts them one by one in a furnace. It also handles potential errors like invalid fuels or inputs, and closes the furnace after finishing the smelting process.", + "type": "comment" + }, + "220": { + "file_id": 22, + "content": " else {\n bot.chat(\n `Failed to smelt ${itemName}, please check the fuel and input.`\n );\n _smeltItemFailCount++;\n if (_smeltItemFailCount > 10) {\n throw new Error(\n `smeltItem failed too many times, please check the fuel and input.`\n );\n }\n }\n}", + "type": "code", + "location": "/voyager/control_primitives/smeltItem.js:57-68" + }, + "221": { + "file_id": 22, + "content": "Else block executed when smelting fails, notifies user to check fuel and input, increments fail count, throws an error after 10 failures for further investigation.", + "type": "comment" + }, + "222": { + "file_id": 23, + "content": "/voyager/control_primitives/useChest.js", + "type": "filepath" + }, + "223": { + "file_id": 23, + "content": "This function enables bot-controlled chest interaction, allowing users to teleport, open/close chests, list items, emit events, and search by name.", + "type": "summary" + }, + "224": { + "file_id": 23, + "content": "async function getItemFromChest(bot, chestPosition, itemsToGet) {\n // return if chestPosition is not Vec3\n if (!(chestPosition instanceof Vec3)) {\n bot.chat(\"chestPosition for getItemFromChest must be a Vec3\");\n return;\n }\n await moveToChest(bot, chestPosition);\n const chestBlock = bot.blockAt(chestPosition);\n const chest = await bot.openContainer(chestBlock);\n for (const name in itemsToGet) {\n const itemByName = mcData.itemsByName[name];\n if (!itemByName) {\n bot.chat(`No item named ${name}`);\n continue;\n }\n const item = chest.findContainerItem(itemByName.id);\n if (!item) {\n bot.chat(`I don't see ${name} in this chest`);\n continue;\n }\n try {\n await chest.withdraw(item.type, null, itemsToGet[name]);\n } catch (err) {\n bot.chat(`Not enough ${name} in chest.`);\n }\n }\n await closeChest(bot, chestBlock);\n}\nasync function depositItemIntoChest(bot, chestPosition, itemsToDeposit) {", + "type": "code", + "location": "/voyager/control_primitives/useChest.js:1-31" + }, + "225": { + "file_id": 23, + "content": "Function to retrieve items from a chest at specified position using Vec3. Moves the bot to the chest location, opens it, and retrieves specific items from the container. Displays error messages if an item is not found or insufficient quantity exists in the chest. Closes the chest after completion.", + "type": "comment" + }, + "226": { + "file_id": 23, + "content": " // return if chestPosition is not Vec3\n if (!(chestPosition instanceof Vec3)) {\n throw new Error(\n \"chestPosition for depositItemIntoChest must be a Vec3\"\n );\n }\n await moveToChest(bot, chestPosition);\n const chestBlock = bot.blockAt(chestPosition);\n const chest = await bot.openContainer(chestBlock);\n for (const name in itemsToDeposit) {\n const itemByName = mcData.itemsByName[name];\n if (!itemByName) {\n bot.chat(`No item named ${name}`);\n continue;\n }\n const item = bot.inventory.findInventoryItem(itemByName.id);\n if (!item) {\n bot.chat(`No ${name} in inventory`);\n continue;\n }\n try {\n await chest.deposit(item.type, null, itemsToDeposit[name]);\n } catch (err) {\n bot.chat(`Not enough ${name} in inventory.`);\n }\n }\n await closeChest(bot, chestBlock);\n}\nasync function checkItemInsideChest(bot, chestPosition) {\n // return if chestPosition is not Vec3", + "type": "code", + "location": "/voyager/control_primitives/useChest.js:32-62" + }, + "227": { + "file_id": 23, + "content": "Code snippet checks if chestPosition is a Vec3, then moves to the chest position, opens the chest, iterates through itemsToDeposit, deposits each item into the chest if available, and finally closes the chest.", + "type": "comment" + }, + "228": { + "file_id": 23, + "content": " if (!(chestPosition instanceof Vec3)) {\n throw new Error(\n \"chestPosition for depositItemIntoChest must be a Vec3\"\n );\n }\n await moveToChest(bot, chestPosition);\n const chestBlock = bot.blockAt(chestPosition);\n await bot.openContainer(chestBlock);\n await closeChest(bot, chestBlock);\n}\nasync function moveToChest(bot, chestPosition) {\n if (!(chestPosition instanceof Vec3)) {\n throw new Error(\n \"chestPosition for depositItemIntoChest must be a Vec3\"\n );\n }\n if (chestPosition.distanceTo(bot.entity.position) > 32) {\n bot.chat(\n `/tp ${chestPosition.x} ${chestPosition.y} ${chestPosition.z}`\n );\n await bot.waitForTicks(20);\n }\n const chestBlock = bot.blockAt(chestPosition);\n if (chestBlock.name !== \"chest\") {\n bot.emit(\"removeChest\", chestPosition);\n throw new Error(\n `No chest at ${chestPosition}, it is ${chestBlock.name}`\n );\n }\n await bot.pathfinder.goto(\n new GoalLookAtBlock(chestBlock.position, bot.world, {})", + "type": "code", + "location": "/voyager/control_primitives/useChest.js:63-94" + }, + "229": { + "file_id": 23, + "content": "Code snippet handles depositing an item into a chest. First, it ensures the 'chestPosition' is a Vec3 instance and throws an error if not. It then moves the bot to the specified chest position using teleportation if it's more than 32 blocks away. After reaching the chest, it checks whether the block at that position is indeed a chest or not. If not, it removes the chest and throws an error with the current block name.", + "type": "comment" + }, + "230": { + "file_id": 23, + "content": " );\n return chestBlock;\n}\nasync function listItemsInChest(bot, chestBlock) {\n const chest = await bot.openContainer(chestBlock);\n const items = chest.containerItems();\n if (items.length > 0) {\n const itemNames = items.reduce((acc, obj) => {\n if (acc[obj.name]) {\n acc[obj.name] += obj.count;\n } else {\n acc[obj.name] = obj.count;\n }\n return acc;\n }, {});\n bot.emit(\"closeChest\", itemNames, chestBlock.position);\n } else {\n bot.emit(\"closeChest\", {}, chestBlock.position);\n }\n return chest;\n}\nasync function closeChest(bot, chestBlock) {\n try {\n const chest = await listItemsInChest(bot, chestBlock);\n await chest.close();\n } catch (err) {\n await bot.closeWindow(chestBlock);\n }\n}\nfunction itemByName(items, name) {\n for (let i = 0; i < items.length; ++i) {\n const item = items[i];\n if (item && item.name === name) return item;\n }\n return null;\n}", + "type": "code", + "location": "/voyager/control_primitives/useChest.js:95-133" + }, + "231": { + "file_id": 23, + "content": "This code defines functions for interacting with chests in a game. It opens the chest, lists items inside and emits corresponding events, closes the chest after interaction, and provides an itemByName function to find an item by name from a list of items.", + "type": "comment" + }, + "232": { + "file_id": 24, + "content": "/voyager/control_primitives/waitForMobRemoved.js", + "type": "filepath" + }, + "233": { + "file_id": 24, + "content": "Function `waitForMobRemoved` waits for entity removal, stops attacking on timeout, resolves with item or rejects with error using event listeners.", + "type": "summary" + }, + "234": { + "file_id": 24, + "content": "function waitForMobRemoved(bot, entity, timeout = 300) {\n return new Promise((resolve, reject) => {\n let success = false;\n let droppedItem = null;\n // Set up timeout\n const timeoutId = setTimeout(() => {\n success = false;\n bot.pvp.stop();\n }, timeout * 1000);\n // Function to handle entityRemoved event\n function onEntityGone(e) {\n if (e === entity) {\n success = true;\n clearTimeout(timeoutId);\n bot.chat(`Killed ${entity.name}!`);\n bot.pvp.stop();\n }\n }\n function onItemDrop(item) {\n if (entity.position.distanceTo(item.position) <= 1) {\n droppedItem = item;\n }\n }\n function onStoppedAttacking() {\n clearTimeout(timeoutId);\n bot.removeListener(\"entityGone\", onEntityGone);\n bot.removeListener(\"stoppedAttacking\", onStoppedAttacking);\n bot.removeListener(\"itemDrop\", onItemDrop);", + "type": "code", + "location": "/voyager/control_primitives/waitForMobRemoved.js:1-31" + }, + "235": { + "file_id": 24, + "content": "Function waitForMobRemoved sets up a timeout to check if a specified entity is removed, handling entityRemoved event, stops attacking when timeout ends, and notifies upon successful removal.", + "type": "comment" + }, + "236": { + "file_id": 24, + "content": " if (!success) reject(new Error(`Failed to kill ${entity.name}.`));\n else resolve(droppedItem);\n }\n // Listen for entityRemoved event\n bot.on(\"entityGone\", onEntityGone);\n bot.on(\"stoppedAttacking\", onStoppedAttacking);\n bot.on(\"itemDrop\", onItemDrop);\n });\n}\nfunction waitForMobShot(bot, entity, timeout = 300) {\n return new Promise((resolve, reject) => {\n let success = false;\n let droppedItem = null;\n // Set up timeout\n const timeoutId = setTimeout(() => {\n success = false;\n bot.hawkEye.stop();\n }, timeout * 1000);\n // Function to handle entityRemoved event\n function onEntityGone(e) {\n if (e === entity) {\n success = true;\n clearTimeout(timeoutId);\n bot.chat(`Shot ${entity.name}!`);\n bot.hawkEye.stop();\n }\n }\n function onItemDrop(item) {\n if (entity.position.distanceTo(item.position) <= 1) {", + "type": "code", + "location": "/voyager/control_primitives/waitForMobRemoved.js:32-65" + }, + "237": { + "file_id": 24, + "content": "This code sets up a timeout and event listeners to wait for the entity to be killed. It resolves with the dropped item if successful, or rejects with an error if it fails. The `onEntityGone` function handles the \"entityRemoved\" event, and the `onItemDrop` function handles the \"itemDrop\" event.", + "type": "comment" + }, + "238": { + "file_id": 24, + "content": " droppedItem = item;\n }\n }\n function onAutoShotStopped() {\n clearTimeout(timeoutId);\n bot.removeListener(\"entityGone\", onEntityGone);\n bot.removeListener(\"auto_shot_stopped\", onAutoShotStopped);\n bot.removeListener(\"itemDrop\", onItemDrop);\n if (!success) reject(new Error(`Failed to shoot ${entity.name}.`));\n else resolve(droppedItem);\n }\n // Listen for entityRemoved event\n bot.on(\"entityGone\", onEntityGone);\n bot.on(\"auto_shot_stopped\", onAutoShotStopped);\n bot.on(\"itemDrop\", onItemDrop);\n });\n}", + "type": "code", + "location": "/voyager/control_primitives/waitForMobRemoved.js:66-84" + }, + "239": { + "file_id": 24, + "content": "Function `waitForMobRemoved` uses event listeners to detect when an entity is removed, stopped auto shot, or an item is dropped. It resolves with the dropped item if successful, otherwise rejects with an error.", + "type": "comment" + }, + "240": { + "file_id": 25, + "content": "/voyager/control_primitives_context/__init__.py", + "type": "filepath" + }, + "241": { + "file_id": 25, + "content": "This function loads control primitives context from the \"voyager\" package. It first determines the package path, then checks for primitive names in the directory if none are provided. It retrieves and returns a list of loaded text primitives.", + "type": "summary" + }, + "242": { + "file_id": 25, + "content": "import pkg_resources\nimport os\nimport voyager.utils as U\ndef load_control_primitives_context(primitive_names=None):\n package_path = pkg_resources.resource_filename(\"voyager\", \"\")\n if primitive_names is None:\n primitive_names = [\n primitive[:-3]\n for primitive in os.listdir(f\"{package_path}/control_primitives_context\")\n if primitive.endswith(\".js\")\n ]\n primitives = [\n U.load_text(f\"{package_path}/control_primitives_context/{primitive_name}.js\")\n for primitive_name in primitive_names\n ]\n return primitives", + "type": "code", + "location": "/voyager/control_primitives_context/__init__.py:1-18" + }, + "243": { + "file_id": 25, + "content": "This function loads control primitives context from the \"voyager\" package. It first determines the package path, then checks for primitive names in the directory if none are provided. It retrieves and returns a list of loaded text primitives.", + "type": "comment" + }, + "244": { + "file_id": 26, + "content": "/voyager/control_primitives_context/craftItem.js", + "type": "filepath" + }, + "245": { + "file_id": 26, + "content": "Crafts specified item by following the recipe, requires a crafting table nearby.", + "type": "summary" + }, + "246": { + "file_id": 26, + "content": "// Craft 8 oak_planks from 2 oak_log (do the recipe 2 times): craftItem(bot, \"oak_planks\", 2);\n// You must place a crafting table before calling this function\nasync function craftItem(bot, name, count = 1) {\n const item = mcData.itemsByName[name];\n const craftingTable = bot.findBlock({\n matching: mcData.blocksByName.crafting_table.id,\n maxDistance: 32,\n });\n await bot.pathfinder.goto(\n new GoalLookAtBlock(craftingTable.position, bot.world)\n );\n const recipe = bot.recipesFor(item.id, null, 1, craftingTable)[0];\n await bot.craft(recipe, count, craftingTable);\n}", + "type": "code", + "location": "/voyager/control_primitives_context/craftItem.js:1-14" + }, + "247": { + "file_id": 26, + "content": "Crafts specified item by following the recipe, requires a crafting table nearby.", + "type": "comment" + }, + "248": { + "file_id": 27, + "content": "/voyager/control_primitives_context/exploreUntil.js", + "type": "filepath" + }, + "249": { + "file_id": 27, + "content": "This code uses the exploreUntil function to search for iron ore underground and pigs on the surface, with exploration stopping after 60 seconds or upon meeting a callback condition. It is within a Voyager control function and may define/manipulate exploration-related primitives.", + "type": "summary" + }, + "250": { + "file_id": 27, + "content": "/*\nExplore until find an iron_ore, use Vec3(0, -1, 0) because iron ores are usually underground\nawait exploreUntil(bot, new Vec3(0, -1, 0), 60, () => {\n const iron_ore = bot.findBlock({\n matching: mcData.blocksByName[\"iron_ore\"].id,\n maxDistance: 32,\n });\n return iron_ore;\n});\nExplore until find a pig, use Vec3(1, 0, 1) because pigs are usually on the surface\nlet pig = await exploreUntil(bot, new Vec3(1, 0, 1), 60, () => {\n const pig = bot.nearestEntity((entity) => {\n return (\n entity.name === \"pig\" &&\n entity.position.distanceTo(bot.entity.position) < 32\n );\n });\n return pig;\n});\n*/\nasync function exploreUntil(bot, direction, maxTime = 60, callback) {\n /*\n Implementation of this function is omitted.\n direction: Vec3, can only contain value of -1, 0 or 1\n maxTime: number, the max time for exploration\n callback: function, early stop condition, will be called each second, exploration will stop if return value is not null\n Return: null if explore timeout, otherwise return the return value of callback", + "type": "code", + "location": "/voyager/control_primitives_context/exploreUntil.js:1-29" + }, + "251": { + "file_id": 27, + "content": "This code uses the exploreUntil function to find an iron ore underground and a pig on the surface. It uses Vec3 direction vectors for searching, with exploration stopping after 60 seconds or if the callback condition is met early. The exploreUntil function takes parameters such as bot, direction, maxTime, and callback for exploration control.", + "type": "comment" + }, + "252": { + "file_id": 27, + "content": " */\n}", + "type": "code", + "location": "/voyager/control_primitives_context/exploreUntil.js:30-31" + }, + "253": { + "file_id": 27, + "content": "This code block appears to be within a function that controls the exploration of the Voyager. The code ends with a closing brace, which may indicate that this section is defining or manipulating some control primitives in the context of exploration.", + "type": "comment" + }, + "254": { + "file_id": 28, + "content": "/voyager/control_primitives_context/killMob.js", + "type": "filepath" + }, + "255": { + "file_id": 28, + "content": "Kill a pig and collect the dropped item function called \"killMob\" which uses bot's nearestEntity to find the target mob, then attacks it and moves towards its position using pathfinder.", + "type": "summary" + }, + "256": { + "file_id": 28, + "content": "// Kill a pig and collect the dropped item: killMob(bot, \"pig\", 300);\nasync function killMob(bot, mobName, timeout = 300) {\n const entity = bot.nearestEntity(\n (entity) =>\n entity.name === mobName &&\n entity.position.distanceTo(bot.entity.position) < 32\n );\n await bot.pvp.attack(entity);\n await bot.pathfinder.goto(\n new GoalBlock(entity.position.x, entity.position.y, entity.position.z)\n );\n}", + "type": "code", + "location": "/voyager/control_primitives_context/killMob.js:1-12" + }, + "257": { + "file_id": 28, + "content": "Kill a pig and collect the dropped item function called \"killMob\" which uses bot's nearestEntity to find the target mob, then attacks it and moves towards its position using pathfinder.", + "type": "comment" + }, + "258": { + "file_id": 29, + "content": "/voyager/control_primitives_context/mineBlock.js", + "type": "filepath" + }, + "259": { + "file_id": 29, + "content": "This function mines a specified block (e.g., stone) by finding and collecting the blocks within a 32-distance range, up to the count specified. It ignores \"No Path\" errors during collection.", + "type": "summary" + }, + "260": { + "file_id": 29, + "content": "// Mine 3 cobblestone: mineBlock(bot, \"stone\", 3);\nasync function mineBlock(bot, name, count = 1) {\n const blocks = bot.findBlocks({\n matching: (block) => {\n return block.name === name;\n },\n maxDistance: 32,\n count: count,\n });\n const targets = [];\n for (let i = 0; i < Math.min(blocks.length, count); i++) {\n targets.push(bot.blockAt(blocks[i]));\n }\n await bot.collectBlock.collect(targets, { ignoreNoPath: true });\n}", + "type": "code", + "location": "/voyager/control_primitives_context/mineBlock.js:1-15" + }, + "261": { + "file_id": 29, + "content": "This function mines a specified block (e.g., stone) by finding and collecting the blocks within a 32-distance range, up to the count specified. It ignores \"No Path\" errors during collection.", + "type": "comment" + }, + "262": { + "file_id": 30, + "content": "/voyager/control_primitives_context/mineflayer.js", + "type": "filepath" + }, + "263": { + "file_id": 30, + "content": "The code allows the Mineflayer bot to move, change equipment, target blocks/entities using goals and functions. It demonstrates how to activate an item and use it on an entity in proper order for functionality.", + "type": "summary" + }, + "264": { + "file_id": 30, + "content": "await bot.pathfinder.goto(goal); // A very useful function. This function may change your main-hand equipment.\n// Following are some Goals you can use:\nnew GoalNear(x, y, z, range); // Move the bot to a block within the specified range of the specified block. `x`, `y`, `z`, and `range` are `number`\nnew GoalXZ(x, z); // Useful for long-range goals that don't have a specific Y level. `x` and `z` are `number`\nnew GoalGetToBlock(x, y, z); // Not get into the block, but get directly adjacent to it. Useful for fishing, farming, filling bucket, and beds. `x`, `y`, and `z` are `number`\nnew GoalFollow(entity, range); // Follow the specified entity within the specified range. `entity` is `Entity`, `range` is `number`\nnew GoalPlaceBlock(position, bot.world, {}); // Position the bot in order to place a block. `position` is `Vec3`\nnew GoalLookAtBlock(position, bot.world, {}); // Path into a position where a blockface of the block at position is visible. `position` is `Vec3`\n// These are other Mineflayer functions you can use:", + "type": "code", + "location": "/voyager/control_primitives_context/mineflayer.js:1-10" + }, + "265": { + "file_id": 30, + "content": "This code contains various goals and functions that can be used with the Mineflayer bot for movement, equipment changes, and targeting specific blocks or entities. The `goto` function allows the bot to move towards a specified goal location, while other goals like `GoalNear`, `GoalXZ`, `GoalGetToBlock`, `GoalFollow`, `GoalPlaceBlock`, and `GoalLookAtBlock` provide more specific options for targeting and movement.", + "type": "comment" + }, + "266": { + "file_id": 30, + "content": "bot.isABed(bedBlock); // Return true if `bedBlock` is a bed\nbot.blockAt(position); // Return the block at `position`. `position` is `Vec3`\n// These are other Mineflayer async functions you can use:\nawait bot.equip(item, destination); // Equip the item in the specified destination. `item` is `Item`, `destination` can only be \"hand\", \"head\", \"torso\", \"legs\", \"feet\", \"off-hand\"\nawait bot.consume(); // Consume the item in the bot's hand. You must equip the item to consume first. Useful for eating food, drinking potions, etc.\nawait bot.fish(); // Let bot fish. Before calling this function, you must first get to a water block and then equip a fishing rod. The bot will automatically stop fishing when it catches a fish\nawait bot.sleep(bedBlock); // Sleep until sunrise. You must get to a bed block first\nawait bot.activateBlock(block); // This is the same as right-clicking a block in the game. Useful for buttons, doors, etc. You must get to the block first\nawait bot.lookAt(position); // Look at the spec", + "type": "code", + "location": "/voyager/control_primitives_context/mineflayer.js:11-20" + }, + "267": { + "file_id": 30, + "content": "The code provides a bot object with various Minecraft-related functions. The 'bot.isABed()' checks if 'bedBlock' is a bed, while 'bot.blockAt()' retrieves the block at specified position. Other functions allow equipping items, consuming items, fishing, sleeping until sunrise, activating blocks, and looking at specific positions using the bot object.", + "type": "comment" + }, + "268": { + "file_id": 30, + "content": "ified position. You must go near the position before you look at it. To fill bucket with water, you must lookAt first. `position` is `Vec3`\nawait bot.activateItem(); // This is the same as right-clicking to use the item in the bot's hand. Useful for using buckets, etc. You must equip the item to activate first\nawait bot.useOn(entity); // This is the same as right-clicking an entity in the game. Useful for shearing sheep, equipping harnesses, etc. You must get to the entity first", + "type": "code", + "location": "/voyager/control_primitives_context/mineflayer.js:20-22" + }, + "269": { + "file_id": 30, + "content": "The code snippet is from the Voyager project's \"mineflayer.js\" file, where it demonstrates how to activate an item (such as a bucket) and use it on an entity (e.g., filling the bucket with water). The sequence starts by activating the item using \"await bot.activateItem();\", followed by using the item on the entity with \"await bot.useOn(entity);\". It's crucial to follow this order - equip the item first and then use it on the entity for proper functionality in the Minecraft game.", + "type": "comment" + }, + "270": { + "file_id": 31, + "content": "/voyager/control_primitives_context/placeItem.js", + "type": "filepath" + }, + "271": { + "file_id": 31, + "content": "The function places an item near the player, finds the inventory item by name, searches for a reference block to place it on, determines position and facing direction, uses pathfinder to go there, and finally places the item. The code snippet equips an item in the bot's hand and then places a block relative to a reference block.", + "type": "summary" + }, + "272": { + "file_id": 31, + "content": "// Place a crafting_table near the player, Vec3(1, 0, 0) is just an example, you shouldn't always use that: placeItem(bot, \"crafting_table\", bot.entity.position.offset(1, 0, 0));\nasync function placeItem(bot, name, position) {\n const item = bot.inventory.findInventoryItem(mcData.itemsByName[name].id);\n // find a reference block\n const faceVectors = [\n new Vec3(0, 1, 0),\n new Vec3(0, -1, 0),\n new Vec3(1, 0, 0),\n new Vec3(-1, 0, 0),\n new Vec3(0, 0, 1),\n new Vec3(0, 0, -1),\n ];\n let referenceBlock = null;\n let faceVector = null;\n for (const vector of faceVectors) {\n const block = bot.blockAt(position.minus(vector));\n if (block?.name !== \"air\") {\n referenceBlock = block;\n faceVector = vector;\n break;\n }\n }\n // You must first go to the block position you want to place\n await bot.pathfinder.goto(new GoalPlaceBlock(position, bot.world, {}));\n // You must equip the item right before calling placeBlock", + "type": "code", + "location": "/voyager/control_primitives_context/placeItem.js:1-25" + }, + "273": { + "file_id": 31, + "content": "This function places an item near the player. It first finds the inventory item by name, then searches for a reference block to place it on. After finding a suitable position and facing direction, the bot goes to that position using the pathfinder, and finally places the item.", + "type": "comment" + }, + "274": { + "file_id": 31, + "content": " await bot.equip(item, \"hand\");\n await bot.placeBlock(referenceBlock, faceVector);\n}", + "type": "code", + "location": "/voyager/control_primitives_context/placeItem.js:26-28" + }, + "275": { + "file_id": 31, + "content": "The code snippet equips an item in the bot's hand and then places a block relative to a reference block.", + "type": "comment" + }, + "276": { + "file_id": 32, + "content": "/voyager/control_primitives_context/smeltItem.js", + "type": "filepath" + }, + "277": { + "file_id": 32, + "content": "This function smelts a specified item using another item as fuel in a furnace. It finds the corresponding blocks for the items and furnace, then performs actions to smelt the items. The loop repeats for the specified count of items, waiting 12 seconds between each smelt. Finally, it closes the furnace after completion.", + "type": "summary" + }, + "278": { + "file_id": 32, + "content": "// Smelt 1 raw_iron into 1 iron_ingot using 1 oak_planks as fuel: smeltItem(bot, \"raw_iron\", \"oak_planks\");\n// You must place a furnace before calling this function\nasync function smeltItem(bot, itemName, fuelName, count = 1) {\n const item = mcData.itemsByName[itemName];\n const fuel = mcData.itemsByName[fuelName];\n const furnaceBlock = bot.findBlock({\n matching: mcData.blocksByName.furnace.id,\n maxDistance: 32,\n });\n await bot.pathfinder.goto(\n new GoalLookAtBlock(furnaceBlock.position, bot.world)\n );\n const furnace = await bot.openFurnace(furnaceBlock);\n for (let i = 0; i < count; i++) {\n await furnace.putFuel(fuel.id, null, 1);\n await furnace.putInput(item.id, null, 1);\n // Wait 12 seconds for the furnace to smelt the item\n await bot.waitForTicks(12 * 20);\n await furnace.takeOutput();\n }\n await furnace.close();\n}", + "type": "code", + "location": "/voyager/control_primitives_context/smeltItem.js:1-22" + }, + "279": { + "file_id": 32, + "content": "This function smelts a specified item using another item as fuel in a furnace. It finds the corresponding blocks for the items and furnace, then performs actions to smelt the items. The loop repeats for the specified count of items, waiting 12 seconds between each smelt. Finally, it closes the furnace after completion.", + "type": "comment" + }, + "280": { + "file_id": 33, + "content": "/voyager/control_primitives_context/useChest.js", + "type": "filepath" + }, + "281": { + "file_id": 33, + "content": "The code contains functions to iterate through items to deposit, find items in bot's inventory, and deposit them into chests. It also checks items inside specific chests using bot's actions for chest interactions.", + "type": "summary" + }, + "282": { + "file_id": 33, + "content": "// Get a torch from chest at (30, 65, 100): getItemFromChest(bot, new Vec3(30, 65, 100), {\"torch\": 1});\n// This function will work no matter how far the bot is from the chest.\nasync function getItemFromChest(bot, chestPosition, itemsToGet) {\n await moveToChest(bot, chestPosition);\n const chestBlock = bot.blockAt(chestPosition);\n const chest = await bot.openContainer(chestBlock);\n for (const name in itemsToGet) {\n const itemByName = mcData.itemsByName[name];\n const item = chest.findContainerItem(itemByName.id);\n await chest.withdraw(item.type, null, itemsToGet[name]);\n }\n await closeChest(bot, chestBlock);\n}\n// Deposit a torch into chest at (30, 65, 100): depositItemIntoChest(bot, new Vec3(30, 65, 100), {\"torch\": 1});\n// This function will work no matter how far the bot is from the chest.\nasync function depositItemIntoChest(bot, chestPosition, itemsToDeposit) {\n await moveToChest(bot, chestPosition);\n const chestBlock = bot.blockAt(chestPosition);\n const chest = await bot.openContainer(chestBlock);", + "type": "code", + "location": "/voyager/control_primitives_context/useChest.js:1-19" + }, + "283": { + "file_id": 33, + "content": "Gets a torch from the chest at position (30, 65, 100) and moves bot to it, opens the chest, retrieves the torch item, closes the chest, and returns.\n\nDeposits a torch into the chest at position (30, 65, 100) and moves bot to it, opens the chest, deposits the torch item, closes the chest, and returns.", + "type": "comment" + }, + "284": { + "file_id": 33, + "content": " for (const name in itemsToDeposit) {\n const itemByName = mcData.itemsByName[name];\n const item = bot.inventory.findInventoryItem(itemByName.id);\n await chest.deposit(item.type, null, itemsToDeposit[name]);\n }\n await closeChest(bot, chestBlock);\n}\n// Check the items inside the chest at (30, 65, 100): checkItemInsideChest(bot, new Vec3(30, 65, 100));\n// You only need to call this function once without any action to finish task of checking items inside the chest.\nasync function checkItemInsideChest(bot, chestPosition) {\n await moveToChest(bot, chestPosition);\n const chestBlock = bot.blockAt(chestPosition);\n await bot.openContainer(chestBlock);\n // You must close the chest after opening it if you are asked to open a chest\n await closeChest(bot, chestBlock);\n}", + "type": "code", + "location": "/voyager/control_primitives_context/useChest.js:20-35" + }, + "285": { + "file_id": 33, + "content": "The code contains a function that iterates through itemsToDeposit, finds the item in bot's inventory, and deposits it into the chest. It also includes another function for checking items inside a specific chest using bot's actions. The functions work together to perform actions related to chest interactions.", + "type": "comment" + }, + "286": { + "file_id": 34, + "content": "/voyager/env/__init__.py", + "type": "filepath" + }, + "287": { + "file_id": 34, + "content": "The code imports the VoyagerEnv class from the bridge module to be used in this package. It appears to be an environment setting or configuration for a project called \"Voyager\".", + "type": "summary" + }, + "288": { + "file_id": 34, + "content": "from .bridge import VoyagerEnv", + "type": "code", + "location": "/voyager/env/__init__.py:1-1" + }, + "289": { + "file_id": 34, + "content": "The code imports the VoyagerEnv class from the bridge module to be used in this package. It appears to be an environment setting or configuration for a project called \"Voyager\".", + "type": "comment" + }, + "290": { + "file_id": 35, + "content": "/voyager/env/bridge.py", + "type": "filepath" + }, + "291": { + "file_id": 35, + "content": "The `VoyagerEnv` class sets up a Minecraft server using Mineflayer, providing pause/unpause functionality and returning the mineflayer instance with Azure login. It also has methods to reset and render, and classes for stopping, closing, and rendering the server.", + "type": "summary" + }, + "292": { + "file_id": 35, + "content": "import os.path\nimport time\nimport warnings\nfrom typing import SupportsFloat, Any, Tuple, Dict\nimport requests\nimport json\nimport gymnasium as gym\nfrom gymnasium.core import ObsType\nimport voyager.utils as U\nfrom .minecraft_launcher import MinecraftInstance\nfrom .process_monitor import SubprocessMonitor\nclass VoyagerEnv(gym.Env):\n def __init__(\n self,\n mc_port=None,\n azure_login=None,\n server_host=\"http://127.0.0.1\",\n server_port=3000,\n request_timeout=600,\n log_path=\"./logs\",\n ):\n if not mc_port and not azure_login:\n raise ValueError(\"Either mc_port or azure_login must be specified\")\n if mc_port and azure_login:\n warnings.warn(\n \"Both mc_port and mc_login are specified, mc_port will be ignored\"\n )\n self.mc_port = mc_port\n self.azure_login = azure_login\n self.server = f\"{server_host}:{server_port}\"\n self.server_port = server_port\n self.request_timeout = request_timeout", + "type": "code", + "location": "/voyager/env/bridge.py:1-38" + }, + "293": { + "file_id": 35, + "content": "This code defines a class `VoyagerEnv` that inherits from `gym.Env`, which represents an environment in the gymnasium framework. The constructor takes parameters such as `mc_port`, `azure_login`, `server_host`, `server_port`, `request_timeout`, and `log_path`. If neither `mc_port` nor `azure_login` is specified, a ValueError will be raised. If both are specified, a warning is logged and `mc_port` is ignored. The class likely interacts with Minecraft instances using the `MinecraftInstance` and `SubprocessMonitor` classes.", + "type": "comment" + }, + "294": { + "file_id": 35, + "content": " self.log_path = log_path\n self.mineflayer = self.get_mineflayer_process(server_port)\n if azure_login:\n self.mc_instance = self.get_mc_instance()\n else:\n self.mc_instance = None\n self.has_reset = False\n self.reset_options = None\n self.connected = False\n self.server_paused = False\n def get_mineflayer_process(self, server_port):\n U.f_mkdir(self.log_path, \"mineflayer\")\n file_path = os.path.abspath(os.path.dirname(__file__))\n return SubprocessMonitor(\n commands=[\n \"node\",\n U.f_join(file_path, \"mineflayer/index.js\"),\n str(server_port),\n ],\n name=\"mineflayer\",\n ready_match=r\"Server started on port (\\d+)\",\n log_path=U.f_join(self.log_path, \"mineflayer\"),\n )\n def get_mc_instance(self):\n print(\"Creating Minecraft server\")\n U.f_mkdir(self.log_path, \"minecraft\")\n return MinecraftInstance(\n **self.azure_login,", + "type": "code", + "location": "/voyager/env/bridge.py:39-68" + }, + "295": { + "file_id": 35, + "content": "This code sets up a Minecraft server using Mineflayer and MinecraftInstance. It initializes the necessary variables, gets the mineflayer process running with specified server port, creates directories for logs, and returns the mineflayer instance if Azure login is provided, or None otherwise.", + "type": "comment" + }, + "296": { + "file_id": 35, + "content": " mineflayer=self.mineflayer,\n log_path=U.f_join(self.log_path, \"minecraft\"),\n )\n def check_process(self):\n if self.mc_instance and not self.mc_instance.is_running:\n # if self.mc_instance:\n # self.mc_instance.check_process()\n # if not self.mc_instance.is_running:\n print(\"Starting Minecraft server\")\n self.mc_instance.run()\n self.mc_port = self.mc_instance.port\n self.reset_options[\"port\"] = self.mc_instance.port\n print(f\"Server started on port {self.reset_options['port']}\")\n retry = 0\n while not self.mineflayer.is_running:\n print(\"Mineflayer process has exited, restarting\")\n self.mineflayer.run()\n if not self.mineflayer.is_running:\n if retry > 3:\n raise RuntimeError(\"Mineflayer process failed to start\")\n else:\n continue\n print(self.mineflayer.ready_line)\n res = requests.post(", + "type": "code", + "location": "/voyager/env/bridge.py:69-93" + }, + "297": { + "file_id": 35, + "content": "The code checks if the Minecraft server and Mineflayer processes are running. If not, it starts the Minecraft server and restarts the Mineflayer process if it exits. It also makes a POST request.", + "type": "comment" + }, + "298": { + "file_id": 35, + "content": " f\"{self.server}/start\",\n json=self.reset_options,\n timeout=self.request_timeout,\n )\n if res.status_code != 200:\n self.mineflayer.stop()\n raise RuntimeError(\n f\"Minecraft server reply with code {res.status_code}\"\n )\n return res.json()\n def step(\n self,\n code: str,\n programs: str = \"\",\n ) -> Tuple[ObsType, SupportsFloat, bool, bool, Dict[str, Any]]:\n if not self.has_reset:\n raise RuntimeError(\"Environment has not been reset yet\")\n self.check_process()\n self.unpause()\n data = {\n \"code\": code,\n \"programs\": programs,\n }\n res = requests.post(\n f\"{self.server}/step\", json=data, timeout=self.request_timeout\n )\n if res.status_code != 200:\n raise RuntimeError(\"Failed to step Minecraft server\")\n returned_data = res.json()\n self.pause()\n return json.loads(returned_data)", + "type": "code", + "location": "/voyager/env/bridge.py:94-125" + }, + "299": { + "file_id": 35, + "content": "This code defines a class with methods for resetting the environment, stepping the Minecraft server, and returning observed data. It uses requests library to send HTTP POST requests to the server's \"start\" and \"step\" endpoints, checking status codes and handling errors accordingly. The class also has methods for pausing and unpausing the Minecraft server execution.", + "type": "comment" + } +} \ No newline at end of file diff --git a/docs/data/3.json b/docs/data/3.json new file mode 100644 index 00000000..feb4a914 --- /dev/null +++ b/docs/data/3.json @@ -0,0 +1,539 @@ +{ + "300": { + "file_id": 35, + "content": " def render(self):\n raise NotImplementedError(\"render is not implemented\")\n def reset(\n self,\n *,\n seed=None,\n options=None,\n ) -> Tuple[ObsType, Dict[str, Any]]:\n if options is None:\n options = {}\n if options.get(\"inventory\", {}) and options.get(\"mode\", \"hard\") != \"hard\":\n raise RuntimeError(\"inventory can only be set when options is hard\")\n self.reset_options = {\n \"port\": self.mc_port,\n \"reset\": options.get(\"mode\", \"hard\"),\n \"inventory\": options.get(\"inventory\", {}),\n \"equipment\": options.get(\"equipment\", []),\n \"spread\": options.get(\"spread\", False),\n \"waitTicks\": options.get(\"wait_ticks\", 5),\n \"position\": options.get(\"position\", None),\n }\n self.unpause()\n self.mineflayer.stop()\n time.sleep(1) # wait for mineflayer to exit\n returned_data = self.check_process()\n self.has_reset = True\n self.connected = True", + "type": "code", + "location": "/voyager/env/bridge.py:127-158" + }, + "301": { + "file_id": 35, + "content": "The code defines two methods, \"render\" and \"reset\". The \"render\" method raises a \"NotImplementedError\", indicating it's not implemented. The \"reset\" method takes optional seed and options parameters, sets reset options (port, mode, inventory, equipment, spread, waitTicks, position), stops the mineflayer, waits for it to exit, checks process status, and marks has_reset as True.", + "type": "comment" + }, + "302": { + "file_id": 35, + "content": " # All the reset in step will be soft\n self.reset_options[\"reset\"] = \"soft\"\n self.pause()\n return json.loads(returned_data)\n def close(self):\n self.unpause()\n if self.connected:\n res = requests.post(f\"{self.server}/stop\")\n if res.status_code == 200:\n self.connected = False\n if self.mc_instance:\n self.mc_instance.stop()\n self.mineflayer.stop()\n return not self.connected\n def pause(self):\n if self.mineflayer.is_running and not self.server_paused:\n res = requests.post(f\"{self.server}/pause\")\n if res.status_code == 200:\n self.server_paused = True\n return self.server_paused\n def unpause(self):\n if self.mineflayer.is_running and self.server_paused:\n res = requests.post(f\"{self.server}/pause\")\n if res.status_code == 200:\n self.server_paused = False\n else:\n print(res.json())\n return self.server_paused", + "type": "code", + "location": "/voyager/env/bridge.py:159-189" + }, + "303": { + "file_id": 35, + "content": "This code defines a class with methods to pause and unpause the server, stop the minecraft instance, and close the connection. The reset_options are set to \"soft\" and it returns parsed JSON data from an API request.", + "type": "comment" + }, + "304": { + "file_id": 36, + "content": "/voyager/env/minecraft_launcher.py", + "type": "filepath" + }, + "305": { + "file_id": 36, + "content": "The code creates a MinecraftInstance class, initializes functions for Mineflayer process and log path, runs node command with script path and server port, handles user login and config options management.", + "type": "summary" + }, + "306": { + "file_id": 36, + "content": "import os\nimport re\nimport minecraft_launcher_lib\nimport sys\nimport voyager.utils as U\nfrom .process_monitor import SubprocessMonitor\nclass MinecraftInstance:\n def __init__(\n self,\n client_id,\n redirect_url,\n secret_value,\n version,\n mineflayer,\n log_path=\"logs\",\n ):\n self.client_id = client_id\n self.redirect_url = redirect_url\n self.secret_value = secret_value\n self.version = version\n self.log_path = log_path\n self.mc_dir = minecraft_launcher_lib.utils.get_minecraft_directory()\n self.port = None\n def stop_mineflayer():\n print(\"Stopping mineflayer\")\n try:\n mineflayer.stop()\n except Exception as e:\n print(e)\n self.mc_command = self.get_mc_command()\n self.mc_process = SubprocessMonitor(\n commands=self.mc_command,\n name=\"minecraft\",\n ready_match=r\"Started serving on (\\d+)\",\n log_path=self.log_path,", + "type": "code", + "location": "/voyager/env/minecraft_launcher.py:1-41" + }, + "307": { + "file_id": 36, + "content": "The code defines a class \"MinecraftInstance\" with attributes such as client_id, redirect_url, secret_value, version, mineflayer, log_path, and mc_dir. It initializes a stop_mineflayer() function to stop the mineflayer process, gets the mc_command using get_mc_command(), and creates a SubprocessMonitor for the minecraft process with specified commands, name, ready_match, and log_path.", + "type": "comment" + }, + "308": { + "file_id": 36, + "content": " callback=stop_mineflayer,\n callback_match=r\"\\[Server thread/INFO\\]: bot left the game\",\n finished_callback=stop_mineflayer,\n )\n def get_mineflayer_process(self, server_port):\n U.f_mkdir(self.log_path, \"../mineflayer\")\n file_path = os.path.abspath(os.path.dirname(__file__))\n return SubprocessMonitor(\n commands=[\n \"node\",\n U.f_join(file_path, \"mineflayer/index.js\"),\n str(server_port),\n ],\n name=\"mineflayer\",\n ready_match=r\"Server started on port (\\d+)\",\n log_path=U.f_join(self.log_path, \"mineflayer\"),\n )\n def get_mc_command(self):\n file_path = os.path.abspath(os.path.dirname(__file__))\n if not U.f_exists(file_path, \"config.json\"):\n (\n login_url,\n state,\n code_verifier,\n ) = minecraft_launcher_lib.microsoft_account.get_secure_login_data(\n self.client_id, self.redirect_url", + "type": "code", + "location": "/voyager/env/minecraft_launcher.py:42-69" + }, + "309": { + "file_id": 36, + "content": "Line 41-68:\n- Initializes a `SubprocessMonitor` for Mineflayer process\n- Sets the log path to \"../mineflayer\" and gets the absolute file path\n- Runs \"node\" command with the script path, server port as arguments\n- Defines ready match pattern as \"Server started on port (\\d+)\"\n- Checks if config.json exists", + "type": "comment" + }, + "310": { + "file_id": 36, + "content": " )\n print(\n f\"Please open {login_url} in your browser and copy the url you are redirected into the prompt below.\"\n )\n code_url = input()\n try:\n auth_code = (\n minecraft_launcher_lib.microsoft_account.parse_auth_code_url(\n code_url, state\n )\n )\n except AssertionError:\n print(\"States do not match!\")\n sys.exit(1)\n except KeyError:\n print(\"Url not valid\")\n sys.exit(1)\n login_data = minecraft_launcher_lib.microsoft_account.complete_login(\n self.client_id,\n self.secret_value,\n self.redirect_url,\n auth_code,\n code_verifier,\n )\n options = {\n \"username\": login_data[\"name\"],\n \"uuid\": login_data[\"id\"],\n \"token\": login_data[\"access_token\"],", + "type": "code", + "location": "/voyager/env/minecraft_launcher.py:70-100" + }, + "311": { + "file_id": 36, + "content": "The code prompts the user to open a login URL in their browser, enter the redirected URL into the prompt, and then parses the authentication code from the entered URL. It validates the state and URL, then completes the login process by retrieving the username, UUID, and access token from the login data.", + "type": "comment" + }, + "312": { + "file_id": 36, + "content": " }\n U.json_dump(options, file_path, \"config.json\")\n print(f\"Login success, save to {U.f_join(file_path, 'config.json')}\")\n options = U.json_load(file_path, \"config.json\")\n mc_command = minecraft_launcher_lib.command.get_minecraft_command(\n self.version, self.mc_dir, options\n )\n return mc_command\n def run(self):\n self.mc_process.run()\n pattern = r\"Started serving on (\\d+)\"\n match = re.search(pattern, self.mc_process.ready_line)\n if match:\n self.port = int(match.group(1))\n print(\"The mc server is listening on port\", self.port)\n else:\n raise RuntimeError(\"Port not found\")\n def stop(self):\n self.mc_process.stop()\n @property\n def is_running(self):\n return self.mc_process.is_running", + "type": "code", + "location": "/voyager/env/minecraft_launcher.py:101-127" + }, + "313": { + "file_id": 36, + "content": "This code defines a class with methods to start, stop, and check the status of a Minecraft server. It loads or saves configuration options, retrieves the Minecraft command, and prints the port when the server is running.", + "type": "comment" + }, + "314": { + "file_id": 37, + "content": "/voyager/env/process_monitor.py", + "type": "filepath" + }, + "315": { + "file_id": 37, + "content": "This code manages a subprocess, defining a class with methods to control its lifecycle and using threading for concurrency. It initializes subprocesses with commands, sets up event handlers for readiness and completion, and monitors output for specific patterns to call callback functions.", + "type": "summary" + }, + "316": { + "file_id": 37, + "content": "import time\nimport re\nimport warnings\nfrom typing import List\nimport psutil\nimport subprocess\nimport logging\nimport threading\nimport voyager.utils as U\nclass SubprocessMonitor:\n def __init__(\n self,\n commands: List[str],\n name: str,\n ready_match: str = r\".*\",\n log_path: str = \"logs\",\n callback_match: str = r\"^(?!x)x$\", # regex that will never match\n callback: callable = None,\n finished_callback: callable = None,\n ):\n self.commands = commands\n start_time = time.strftime(\"%Y%m%d_%H%M%S\")\n self.name = name\n self.logger = logging.getLogger(name)\n handler = logging.FileHandler(U.f_join(log_path, f\"{start_time}.log\"))\n formatter = logging.Formatter(\n \"%(asctime)s - %(name)s - %(levelname)s - %(message)s\"\n )\n handler.setFormatter(formatter)\n self.logger.addHandler(handler)\n self.logger.setLevel(logging.INFO)\n self.process = None\n self.ready_match = ready_match\n self.ready_event = None", + "type": "code", + "location": "/voyager/env/process_monitor.py:1-38" + }, + "317": { + "file_id": 37, + "content": "Class for monitoring subprocesses, taking a list of commands, name, regex patterns for ready and callback conditions, log path, and optional callbacks on process completion. Sets up a logger with time-stamped file handler and initializes process variable.", + "type": "comment" + }, + "318": { + "file_id": 37, + "content": " self.ready_line = None\n self.callback_match = callback_match\n self.callback = callback\n self.finished_callback = finished_callback\n self.thread = None\n def _start(self):\n self.logger.info(f\"Starting subprocess with commands: {self.commands}\")\n self.process = psutil.Popen(\n self.commands,\n stdout=subprocess.PIPE,\n stderr=subprocess.STDOUT,\n universal_newlines=True,\n )\n print(f\"Subprocess {self.name} started with PID {self.process.pid}.\")\n for line in iter(self.process.stdout.readline, \"\"):\n self.logger.info(line.strip())\n if re.search(self.ready_match, line):\n self.ready_line = line\n self.logger.info(\"Subprocess is ready.\")\n self.ready_event.set()\n if re.search(self.callback_match, line):\n self.callback()\n if not self.ready_event.is_set():\n self.ready_event.set()\n warnings.warn(f\"Subprocess {self.name} failed to start.\")", + "type": "code", + "location": "/voyager/env/process_monitor.py:39-65" + }, + "319": { + "file_id": 37, + "content": "This code initializes a subprocess with commands and sets up event handlers for its readiness and completion. It monitors the output of the subprocess, looking for specific patterns in each line to determine when it's ready and to call the callback function. If the process fails to start, it raises a warning.", + "type": "comment" + }, + "320": { + "file_id": 37, + "content": " if self.finished_callback:\n self.finished_callback()\n def run(self):\n self.ready_event = threading.Event()\n self.ready_line = None\n self.thread = threading.Thread(target=self._start)\n self.thread.start()\n self.ready_event.wait()\n def stop(self):\n self.logger.info(\"Stopping subprocess.\")\n if self.process and self.process.is_running():\n self.process.terminate()\n self.process.wait()\n # def __del__(self):\n # if self.process.is_running():\n # self.stop()\n @property\n def is_running(self):\n if self.process is None:\n return False\n return self.process.is_running()", + "type": "code", + "location": "/voyager/env/process_monitor.py:66-90" + }, + "321": { + "file_id": 37, + "content": "This code defines a class that manages a subprocess and provides methods to control its lifecycle, including starting, stopping, and checking if it's still running. It uses the threading module for concurrency and a property to determine if the process is running.", + "type": "comment" + }, + "322": { + "file_id": 38, + "content": "/voyager/prompts/__init__.py", + "type": "filepath" + }, + "323": { + "file_id": 38, + "content": "This code imports required packages, defines a function to load prompts using the voyager.utils module, and uses pkg_resources to obtain the package's file path. It then returns the prompt text using the U.load_text() method.", + "type": "summary" + }, + "324": { + "file_id": 38, + "content": "import pkg_resources\nimport voyager.utils as U\ndef load_prompt(prompt):\n package_path = pkg_resources.resource_filename(\"voyager\", \"\")\n return U.load_text(f\"{package_path}/prompts/{prompt}.txt\")", + "type": "code", + "location": "/voyager/prompts/__init__.py:1-7" + }, + "325": { + "file_id": 38, + "content": "This code imports required packages, defines a function to load prompts using the voyager.utils module, and uses pkg_resources to obtain the package's file path. It then returns the prompt text using the U.load_text() method.", + "type": "comment" + }, + "326": { + "file_id": 39, + "content": "/voyager/prompts/action_response_format.txt", + "type": "filepath" + }, + "327": { + "file_id": 39, + "content": "This code defines a main function named \"yourMainFunctionName\" that takes a parameter called \"bot\". The function is async, meaning it can perform asynchronous operations. It includes helper functions, which are only used if needed and should be avoided when possible. The code also has additional lines of code that may contain instructions or prompts.", + "type": "summary" + }, + "328": { + "file_id": 39, + "content": "Explain: ...\nPlan:\n1) ...\n2) ...\n3) ...\n...\nCode:\n```javascript\n// helper functions (only if needed, try to avoid them)\n...\n// main function after the helper functions\nasync function yourMainFunctionName(bot) {\n // ...\n}\n```", + "type": "code", + "location": "/voyager/prompts/action_response_format.txt:1-15" + }, + "329": { + "file_id": 39, + "content": "This code defines a main function named \"yourMainFunctionName\" that takes a parameter called \"bot\". The function is async, meaning it can perform asynchronous operations. It includes helper functions, which are only used if needed and should be avoided when possible. The code also has additional lines of code that may contain instructions or prompts.", + "type": "comment" + }, + "330": { + "file_id": 40, + "content": "/voyager/prompts/action_template.txt", + "type": "filepath" + }, + "331": { + "file_id": 40, + "content": "The code snippet introduces a reusable Mineflayer assistant using APIs for tasks in Minecraft, with focus on chat, exploration, and event listeners, while ensuring fair gameplay.", + "type": "summary" + }, + "332": { + "file_id": 40, + "content": "You are a helpful assistant that writes Mineflayer javascript code to complete any Minecraft task specified by me.\nHere are some useful programs written with Mineflayer APIs.\n{programs}\nAt each round of conversation, I will give you\nCode from the last round: ...\nExecution error: ...\nChat log: ...\nBiome: ...\nTime: ...\nNearby blocks: ...\nNearby entities (nearest to farthest):\nHealth: ...\nHunger: ...\nPosition: ...\nEquipment: ...\nInventory (xx/36): ...\nChests: ...\nTask: ...\nContext: ...\nCritique: ...\nYou should then respond to me with\nExplain (if applicable): Are there any steps missing in your plan? Why does the code not complete the task? What does the chat log and execution error imply?\nPlan: How to complete the task step by step. You should pay attention to Inventory since it tells what you have. The task completeness check is also based on your final inventory.\nCode:\n 1) Write an async function taking the bot as the only argument.\n 2) Reuse the above useful programs as much as possible.\n - Use `mineBlock(bot, name, count)` to collect blocks. Do not use `bot.dig` directly.", + "type": "code", + "location": "/voyager/prompts/action_template.txt:1-32" + }, + "333": { + "file_id": 40, + "content": "Code snippet introduces a helpful Mineflayer assistant that utilizes Mineflayer APIs to complete tasks in Minecraft. The code includes pre-written programs, requires an async function, and emphasizes reusing existing programs while collecting blocks with `mineBlock(bot, name, count)`.", + "type": "comment" + }, + "334": { + "file_id": 40, + "content": " - Use `craftItem(bot, name, count)` to craft items. Do not use `bot.craft` or `bot.recipesFor` directly.\n - Use `smeltItem(bot, name count)` to smelt items. Do not use `bot.openFurnace` directly.\n - Use `placeItem(bot, name, position)` to place blocks. Do not use `bot.placeBlock` directly.\n - Use `killMob(bot, name, timeout)` to kill mobs. Do not use `bot.attack` directly.\n 3) Your function will be reused for building more complex functions. Therefore, you should make it generic and reusable. You should not make strong assumption about the inventory (as it may be changed at a later time), and therefore you should always check whether you have the required items before using them. If not, you should first collect the required items and reuse the above useful programs.\n 4) Functions in the \"Code from the last round\" section will not be saved or executed. Do not reuse functions listed there.\n 5) Anything defined outside a function will be ignored, define all your variables inside your functions.", + "type": "code", + "location": "/voyager/prompts/action_template.txt:33-39" + }, + "335": { + "file_id": 40, + "content": "Code provides guidelines for crafting, smelting, placing blocks, and killing mobs using specific functions without directly using the respective bot methods. The code emphasizes reusability, avoiding strong assumptions about the inventory, checking for required items before using them, and defining variables inside functions. Functions from previous round are not saved or executed.", + "type": "comment" + }, + "336": { + "file_id": 40, + "content": " 6) Call `bot.chat` to show the intermediate progress.\n 7) Use `exploreUntil(bot, direction, maxDistance, callback)` when you cannot find something. You should frequently call this before mining blocks or killing mobs. You should select a direction at random every time instead of constantly using (1, 0, 1).\n 8) `maxDistance` should always be 32 for `bot.findBlocks` and `bot.findBlock`. Do not cheat.\n 9) Do not write infinite loops or recursive functions.\n 10) Do not use `bot.on` or `bot.once` to register event listeners. You definitely do not need them.\n 11) Name your function in a meaningful way (can infer the task from the name).\nYou should only respond in the format as described below:\nRESPONSE FORMAT:\n{response_format}", + "type": "code", + "location": "/voyager/prompts/action_template.txt:40-49" + }, + "337": { + "file_id": 40, + "content": "Code is providing guidelines for writing code that interacts with a bot, focusing on chat functionality, exploration, and event listener usage. It emphasizes not cheating or using unnecessary functions.", + "type": "comment" + }, + "338": { + "file_id": 41, + "content": "/voyager/prompts/critic.txt", + "type": "filepath" + }, + "339": { + "file_id": 41, + "content": "This code evaluates Minecraft tasks by analyzing user input, inventory data, and offers feedback on progress. It manages tasks like planting, zombie killing, and food eating, checks inventory for success or failure, and suggests item removal if over 20 slots are occupied.", + "type": "summary" + }, + "340": { + "file_id": 41, + "content": "You are an assistant that assesses my progress of playing Minecraft and provides useful guidance.\nYou are required to evaluate if I have met the task requirements. Exceeding the task requirements is also considered a success while failing to meet them requires you to provide critique to help me improve.\nI will give you the following information:\nBiome: The biome after the task execution.\nTime: The current time.\nNearby blocks: The surrounding blocks. These blocks are not collected yet. However, this is useful for some placing or planting tasks.\nHealth: My current health.\nHunger: My current hunger level. For eating task, if my hunger level is 20.0, then I successfully ate the food.\nPosition: My current position.\nEquipment: My final equipment. For crafting tasks, I sometimes equip the crafted item.\nInventory (xx/36): My final inventory. For mining and smelting tasks, you only need to check inventory.\nChests: If the task requires me to place items in a chest, you can find chest information here.\nTask: The objective I need to accomplish.", + "type": "code", + "location": "/voyager/prompts/critic.txt:1-16" + }, + "341": { + "file_id": 41, + "content": "This code outlines the required information and context for assessing progress in Minecraft tasks, including biome, time, nearby blocks, health, hunger, position, equipment, inventory, chests, and task.", + "type": "comment" + }, + "342": { + "file_id": 41, + "content": "Context: The context of the task.\nYou should only respond in JSON format as described below:\n{\n \"reasoning\": \"reasoning\",\n \"success\": boolean,\n \"critique\": \"critique\",\n}\nEnsure the response can be parsed by Python `json.loads`, e.g.: no trailing commas, no single quotes, etc.\nHere are some examples:\nINPUT:\nInventory (2/36): {'oak_log':2, 'spruce_log':2}\nTask: Mine 3 wood logs\nRESPONSE:\n{\n \"reasoning\": \"You need to mine 3 wood logs. You have 2 oak logs and 2 spruce logs, which add up to 4 wood logs.\",\n \"success\": true,\n \"critique\": \"\"\n}\nINPUT:\nInventory (3/36): {'crafting_table': 1, 'spruce_planks': 6, 'stick': 4}\nTask: Craft a wooden pickaxe\nRESPONSE:\n{\n \"reasoning\": \"You have enough materials to craft a wooden pickaxe, but you didn't craft it.\",\n \"success\": false,\n \"critique\": \"Craft a wooden pickaxe with a crafting table using 3 spruce planks and 2 sticks.\"\n}\nINPUT:\nInventory (2/36): {'raw_iron': 5, 'stone_pickaxe': 1}\nTask: Mine 5 iron_ore\nRESPONSE:\n{\n \"reasoning\": \"Mining iron_ore in Minecraft will get raw_iron. You have 5 raw_iron in your inventory.\",", + "type": "code", + "location": "/voyager/prompts/critic.txt:17-59" + }, + "343": { + "file_id": 41, + "content": "Code provides instructions for generating a JSON response based on user input and inventory information, evaluating task completion and offering critique if necessary.", + "type": "comment" + }, + "344": { + "file_id": 41, + "content": " \"success\": true,\n \"critique\": \"\"\n}\nINPUT:\nBiome: plains\nNearby blocks: stone, dirt, grass_block, grass, farmland, wheat\nInventory (26/36): ...\nTask: Plant 1 wheat seed.\nRESPONSE:\n{\n \"reasoning\": \"For planting tasks, inventory information is useless. In nearby blocks, there is farmland and wheat, which means you succeed to plant the wheat seed.\",\n \"success\": true,\n \"critique\": \"\"\n}\nINPUT:\nInventory (11/36): {... ,'rotten_flesh': 1}\nTask: Kill 1 zombie\nContext: ...\nRESPONSE\n{\n \"reasoning\": \"You have rotten flesh in your inventory, which means you successfully killed one zombie.\",\n \"success\": true,\n \"critique\": \"\"\n}\nINPUT:\nHunger: 20.0/20.0\nInventory (11/36): ...\nTask: Eat 1 ...\nContext: ...\nRESPONSE\n{\n \"reasoning\": \"For all eating task, if the player's hunger is 20.0, then the player successfully ate the food.\",\n \"success\": true,\n \"critique\": \"\"\n}\nINPUT:\nNearby blocks: chest\nInventory (28/36): {'rail': 1, 'coal': 2, 'oak_planks': 13, 'copper_block': 1, 'diorite': 7, 'cooked_beef': 4, 'gran", + "type": "code", + "location": "/voyager/prompts/critic.txt:60-113" + }, + "345": { + "file_id": 41, + "content": "This code handles different game tasks such as planting, killing zombies, and eating food. It checks the player's inventory, nearby blocks, or hunger level to determine if the task was successful or not. The reasoning behind each success is provided along with a \"success\" flag and an empty \"critique\" field.", + "type": "comment" + }, + "346": { + "file_id": 41, + "content": "ite': 22, 'cobbled_deepslate': 23, 'feather': 4, 'leather': 2, 'cooked_chicken': 3, 'white_wool': 2, 'stick': 3, 'black_wool': 1, 'stone_sword': 2, 'stone_hoe': 1, 'stone_axe': 2, 'stone_shovel': 2, 'cooked_mutton': 4, 'cobblestone_wall': 18, 'crafting_table': 1, 'furnace': 1, 'iron_pickaxe': 1, 'stone_pickaxe': 1, 'raw_copper': 12}\nChests:\n(81, 131, 16): {'andesite': 2, 'dirt': 2, 'cobblestone': 75, 'wooden_pickaxe': 1, 'wooden_sword': 1}\nTask: Deposit useless items into the chest at (81, 131, 16)\nContext: ...\nRESPONSE\n{\n \"reasoning\": \"You have 28 items in your inventory after depositing, which is more than 20. You need to deposit more items from your inventory to the chest.\",\n \"success\": false,\n \"critique\": \"Deposit more useless items such as copper_block, diorite, granite, cobbled_deepslate, feather, and leather to meet the requirement of having only 20 occupied slots in your inventory.\"\n}", + "type": "code", + "location": "/voyager/prompts/critic.txt:113-127" + }, + "347": { + "file_id": 41, + "content": "Code snippet represents inventory items with their counts, chest location and associated task to deposit useless items. A response is given if the player has more than 20 slots occupied in their inventory, suggesting specific items to remove for compliance.", + "type": "comment" + }, + "348": { + "file_id": 42, + "content": "/voyager/prompts/curriculum.txt", + "type": "filepath" + }, + "349": { + "file_id": 42, + "content": "This Minecraft assistant suggests tasks based on player progress, avoiding building shelters and specifying task types to avoid. The code provides instructions for suggesting appropriate tasks based on game state.", + "type": "summary" + }, + "350": { + "file_id": 42, + "content": "You are a helpful assistant that tells me the next immediate task to do in Minecraft. My ultimate goal is to discover as many diverse things as possible, accomplish as many diverse tasks as possible and become the best Minecraft player in the world.\nI will give you the following information:\nQuestion 1: ...\nAnswer: ...\nQuestion 2: ...\nAnswer: ...\nQuestion 3: ...\nAnswer: ...\n...\nBiome: ...\nTime: ...\nNearby blocks: ...\nOther blocks that are recently seen: ...\nNearby entities (nearest to farthest): ...\nHealth: Higher than 15 means I'm healthy.\nHunger: Higher than 15 means I'm not hungry.\nPosition: ...\nEquipment: If I have better armor in my inventory, you should ask me to equip it.\nInventory (xx/36): ...\nChests: You can ask me to deposit or take items from these chests. There also might be some unknown chest, you should ask me to open and check items inside the unknown chest.\nCompleted tasks so far: ...\nFailed tasks that are too hard: ...\nYou must follow the following criteria:\n1) You should act as a mentor and guide me to the next task based on my current learning progress.", + "type": "code", + "location": "/voyager/prompts/curriculum.txt:1-26" + }, + "351": { + "file_id": 42, + "content": "This code describes a Minecraft assistant that provides the next task based on the player's learning progress, considering their position, health, hunger, equipment, inventory, and completed/failed tasks.", + "type": "comment" + }, + "352": { + "file_id": 42, + "content": "2) Please be very specific about what resources I need to collect, what I need to craft, or what mobs I need to kill.\n3) The next task should follow a concise format, such as \"Mine [quantity] [block]\", \"Craft [quantity] [item]\", \"Smelt [quantity] [item]\", \"Kill [quantity] [mob]\", \"Cook [quantity] [food]\", \"Equip [item]\" etc. It should be a single phrase. Do not propose multiple tasks at the same time. Do not mention anything else.\n4) The next task should not be too hard since I may not have the necessary resources or have learned enough skills to complete it yet.\n5) The next task should be novel and interesting. I should look for rare resources, upgrade my equipment and tools using better materials, and discover new things. I should not be doing the same thing over and over again.\n6) I may sometimes need to repeat some tasks if I need to collect more resources to complete more difficult tasks. Only repeat tasks if necessary.\n7) Do not ask me to build or dig shelter even if it's at night. I want to explore the world and discover new things. I don't want to stay in one place.", + "type": "code", + "location": "/voyager/prompts/curriculum.txt:27-32" + }, + "353": { + "file_id": 42, + "content": "This code provides guidelines for the format of tasks, specifying to be concise, single-phrased, and not too difficult or repetitive. It encourages exploring, resource collection, and equipment upgrades while avoiding building shelters.", + "type": "comment" + }, + "354": { + "file_id": 42, + "content": "8) Tasks that require information beyond the player's status to verify should be avoided. For instance, \"Placing 4 torches\" and \"Dig a 2x1x2 hole\" are not ideal since they require visual confirmation from the screen. All the placing, building, planting, and trading tasks should be avoided. Do not propose task starting with these keywords.\nYou should only respond in the format as described below:\nRESPONSE FORMAT:\nReasoning: Based on the information I listed above, do reasoning about what the next task should be.\nTask: The next task.\nHere's an example response:\nReasoning: The inventory is empty now, chop down a tree to get some wood.\nTask: Obtain a wood log.", + "type": "code", + "location": "/voyager/prompts/curriculum.txt:33-42" + }, + "355": { + "file_id": 42, + "content": "The code provides instructions to avoid certain task types, mentions example of bad tasks, and specifies a response format for suggesting appropriate tasks based on the current game state.", + "type": "comment" + }, + "356": { + "file_id": 43, + "content": "/voyager/prompts/curriculum_qa_step1_ask_questions.txt", + "type": "filepath" + }, + "357": { + "file_id": 43, + "content": "This code generates Minecraft-related prompts based on biome and time, requiring users to follow a specific response format for answering.", + "type": "summary" + }, + "358": { + "file_id": 43, + "content": "You are a helpful assistant that asks questions to help me decide the next immediate task to do in Minecraft. My ultimate goal is to discover as many things as possible, accomplish as many tasks as possible and become the best Minecraft player in the world.\nI will give you the following information:\nBiome: ...\nTime: ...\nNearby blocks: ...\nOther blocks that are recently seen: ...\nNearby entities (nearest to farthest): ...\nHealth: ...\nHunger: ...\nPosition: ...\nEquipment: ...\nInventory (xx/36): ...\nChests: ...\nCompleted tasks so far: ...\nFailed tasks that are too hard: ...\nYou must follow the following criteria:\n1) You should ask at least 5 questions (but no more than 10 questions) to help me decide the next immediate task to do. Each question should be followed by the concept that the question is about.\n2) Your question should be specific to a concept in Minecraft.\n Bad example (the question is too general):\n Question: What is the best way to play Minecraft?\n Concept: unknown\n Bad example (axe is still general, you should specify the type of axe such as wooden axe):", + "type": "code", + "location": "/voyager/prompts/curriculum_qa_step1_ask_questions.txt:1-24" + }, + "359": { + "file_id": 43, + "content": "Code snippet outlines a text file named \"curriculum_qa_step1_ask_questions.txt\" where an AI will provide questions related to Minecraft based on given information like biome, time, nearby blocks, and other relevant factors. The AI should ask at least 5 but not more than 10 specific Minecraft-related questions, adhering to criteria specified in the file.", + "type": "comment" + }, + "360": { + "file_id": 43, + "content": " What are the benefits of using an axe to gather resources?\n Concept: axe\n Good example:\n Question: How to make a wooden pickaxe?\n Concept: wooden pickaxe\n3) Your questions should be self-contained and not require any context.\n Bad example (the question requires the context of my current biome):\n Question: What are the blocks that I can find in my current biome?\n Concept: unknown\n Bad example (the question requires the context of my current inventory):\n Question: What are the resources you need the most currently?\n Concept: unknown\n Bad example (the question requires the context of my current inventory):\n Question: Do you have any gold or emerald resources?\n Concept: gold\n Bad example (the question requires the context of my nearby entities):\n Question: Can you see any animals nearby that you can kill for food?\n Concept: food\n Bad example (the question requires the context of my nearby blocks):\n Question: Is there any water source nearby?\n Concept: water\n Good example:", + "type": "code", + "location": "/voyager/prompts/curriculum_qa_step1_ask_questions.txt:25-46" + }, + "361": { + "file_id": 43, + "content": "This code is providing examples of good and bad question prompts for the curriculum. Good prompts should be self-contained and not require any context, while bad prompts require additional information such as current biome, inventory, nearby entities or blocks.", + "type": "comment" + }, + "362": { + "file_id": 43, + "content": " Question: What are the blocks that I can find in the sparse jungle?\n Concept: sparse jungle\n4) Do not ask questions about building tasks (such as building a shelter) since they are too hard for me to do.\nLet's say your current biome is sparse jungle. You can ask questions like:\nQuestion: What are the items that I can find in the sparse jungle?\nConcept: sparse jungle\nQuestion: What are the mobs that I can find in the sparse jungle?\nConcept: sparse jungle\nLet's say you see a creeper nearby, and you have not defeated a creeper before. You can ask a question like:\nQuestion: How to defeat the creeper?\nConcept: creeper\nLet's say your last completed task is \"Craft a wooden pickaxe\". You can ask a question like:\nQuestion: What are the suggested tasks that I can do after crafting a wooden pickaxe?\nConcept: wooden pickaxe\nHere are some more question and concept examples:\nQuestion: What are the ores that I can find in the sparse jungle?\nConcept: sparse jungle\n(the above concept should not be \"ore\" because ", + "type": "code", + "location": "/voyager/prompts/curriculum_qa_step1_ask_questions.txt:47-68" + }, + "363": { + "file_id": 43, + "content": "The code provides prompts for asking questions related to specific concepts and tasks within the game. The user can inquire about items, mobs, tasks, or ores found in different biomes, with examples given for sparse jungle, creepers, wooden pickaxes, and various ores.", + "type": "comment" + }, + "364": { + "file_id": 43, + "content": "I need to look up the page of \"sparse jungle\" to find out what ores I can find in the sparse jungle)\nQuestion: How can you obtain food in the sparse jungle?\nConcept: sparse jungle\n(the above concept should not be \"food\" because I need to look up the page of \"sparse jungle\" to find out what food I can obtain in the sparse jungle)\nQuestion: How can you use the furnace to upgrade your equipment and make useful items?\nConcept: furnace\nQuestion: How to obtain a diamond ore?\nConcept: diamond ore\nQuestion: What are the benefits of using a stone pickaxe over a wooden pickaxe?\nConcept: stone pickaxe\nQuestion: What are the tools that you can craft using wood planks and sticks?\nConcept: wood planks\nYou should only respond in the format as described below:\nRESPONSE FORMAT:\nReasoning: ...\nQuestion 1: ...\nConcept 1: ...\nQuestion 2: ...\nConcept 2: ...\nQuestion 3: ...\nConcept 3: ...\nQuestion 4: ...\nConcept 4: ...\nQuestion 5: ...\nConcept 5: ...\n...", + "type": "code", + "location": "/voyager/prompts/curriculum_qa_step1_ask_questions.txt:68-94" + }, + "365": { + "file_id": 43, + "content": "This code is asking for questions related to specific concepts in the game, and requires looking up information for answers. The responses should follow a specific format, including reasoning and the relevant concept being asked about.", + "type": "comment" + }, + "366": { + "file_id": 44, + "content": "/voyager/prompts/curriculum_qa_step2_answer_questions.txt", + "type": "filepath" + }, + "367": { + "file_id": 44, + "content": "The code sets up a helper assistant to answer Minecraft-related questions based on provided context and the assistant's own knowledge. It provides guidelines for formatting answers as well, stating to start with \"Answer: \" and indicate \"Answer: Unknown\" if no answer is available.", + "type": "summary" + }, + "368": { + "file_id": 44, + "content": "You are a helpful assistant that answer my question about Minecraft.\nI will give you the following information:\nQuestion: ...\nYou will answer the question based on the context (only if available and helpful) and your own knowledge of Minecraft.\n1) Start your answer with \"Answer: \".\n2) Answer \"Answer: Unknown\" if you don't know the answer.", + "type": "code", + "location": "/voyager/prompts/curriculum_qa_step2_answer_questions.txt:1-8" + }, + "369": { + "file_id": 44, + "content": "The code sets up a helper assistant to answer Minecraft-related questions based on provided context and the assistant's own knowledge. It provides guidelines for formatting answers as well, stating to start with \"Answer: \" and indicate \"Answer: Unknown\" if no answer is available.", + "type": "comment" + }, + "370": { + "file_id": 45, + "content": "/voyager/prompts/curriculum_task_decomposition.txt", + "type": "filepath" + }, + "371": { + "file_id": 45, + "content": "This code is an AI-generated assistant tasked with decomposing a specified Minecraft task into subgoals based on the user's current inventory. It should return a Python list of these subgoals, each in a specific format, including necessary tools at different levels. The response must be in JSON format to ensure proper parsing by Python's `json.loads`.", + "type": "summary" + }, + "372": { + "file_id": 45, + "content": "You are a helpful assistant that generates a curriculum of subgoals to complete any Minecraft task specified by me.\nI'll give you a final task and my current inventory, you need to decompose the task into a list of subgoals based on my inventory.\nYou must follow the following criteria:\n1) Return a Python list of subgoals that can be completed in order to complete the specified task.\n2) Each subgoal should follow a concise format, such as \"Mine [quantity] [block]\", \"Craft [quantity] [item]\", \"Smelt [quantity] [item]\", \"Kill [quantity] [mob]\", \"Cook [quantity] [food]\", \"Equip [item]\".\n3) Include each level of necessary tools as a subgoal, such as wooden, stone, iron, diamond, etc.\nYou should only respond in JSON format as described below:\n[\"subgoal1\", \"subgoal2\", \"subgoal3\", ...]\nEnsure the response can be parsed by Python `json.loads`, e.g.: no trailing commas, no single quotes, etc.", + "type": "code", + "location": "/voyager/prompts/curriculum_task_decomposition.txt:1-12" + }, + "373": { + "file_id": 45, + "content": "This code is an AI-generated assistant tasked with decomposing a specified Minecraft task into subgoals based on the user's current inventory. It should return a Python list of these subgoals, each in a specific format, including necessary tools at different levels. The response must be in JSON format to ensure proper parsing by Python's `json.loads`.", + "type": "comment" + }, + "374": { + "file_id": 46, + "content": "/voyager/prompts/skill.txt", + "type": "filepath" + }, + "375": { + "file_id": 46, + "content": "The function `mineCobblestone` ensures a wooden pickaxe is available, equips it, and explores to find stone blocks, mining 8 cobblestones each time until the maximum count is reached or no more blocks are found.", + "type": "summary" + }, + "376": { + "file_id": 46, + "content": "You are a helpful assistant that writes a description of the given function written in Mineflayer javascript code.\n1) Do not mention the function name.\n2) Do not mention anything about `bot.chat` or helper functions.\n3) There might be some helper functions before the main function, but you only need to describe the main function.\n4) Try to summarize the function in no more than 6 sentences.\n5) Your response should be a single line of text.\nFor example, if the function is:\nasync function mineCobblestone(bot) {\n // Check if the wooden pickaxe is in the inventory, if not, craft one\n let woodenPickaxe = bot.inventory.findInventoryItem(mcData.itemsByName[\"wooden_pickaxe\"].id);\n if (!woodenPickaxe) {\n bot.chat(\"Crafting a wooden pickaxe.\");\n await craftWoodenPickaxe(bot);\n woodenPickaxe = bot.inventory.findInventoryItem(mcData.itemsByName[\"wooden_pickaxe\"].id);\n }\n // Equip the wooden pickaxe if it exists\n if (woodenPickaxe) {\n await bot.equip(woodenPickaxe, \"hand\");\n // Explore until we find a stone block", + "type": "code", + "location": "/voyager/prompts/skill.txt:1-24" + }, + "377": { + "file_id": 46, + "content": "Function: Explore and collect cobblestone using wooden pickaxe\n- Check if wooden pickaxe is in inventory, craft one if not.\n- Equips wooden pickaxe if found, then explores until stone block is found.\n- Collects stone block when found and places it in the inventory.\n- Repeats process until a set number of cobblestones are collected.\n- Stops if maximum cobblestone count reached or if no more stone blocks are found.", + "type": "comment" + }, + "378": { + "file_id": 46, + "content": " await exploreUntil(bot, new Vec3(1, -1, 1), 60, () => {\n const stone = bot.findBlock({\n matching: mcData.blocksByName[\"stone\"].id,\n maxDistance: 32\n });\n if (stone) {\n return true;\n }\n });\n // Mine 8 cobblestone blocks using the wooden pickaxe\n bot.chat(\"Found a stone block. Mining 8 cobblestone blocks.\");\n await mineBlock(bot, \"stone\", 8);\n bot.chat(\"Successfully mined 8 cobblestone blocks.\");\n // Save the event of mining 8 cobblestone\n bot.save(\"cobblestone_mined\");\n } else {\n bot.chat(\"Failed to craft a wooden pickaxe. Cannot mine cobblestone.\");\n }\n}\nThe main function is `mineCobblestone`.\nThen you would write:\nThe function is about mining 8 cobblestones using a wooden pickaxe. First check if a wooden pickaxe is in the inventory. If not, craft one. If the wooden pickaxe is available, equip the wooden pickaxe in the hand. Next, explore the environment until finding a stone block. Once a stone block is found, mine a total of 8 cobblestone blocks using the wooden pickaxe.", + "type": "code", + "location": "/voyager/prompts/skill.txt:25-51" + }, + "379": { + "file_id": 46, + "content": "The function `mineCobblestone` checks for the availability of a wooden pickaxe in the inventory. If not, it crafts one. Afterward, equips the wooden pickaxe in the hand. It then explores until finding a stone block and mines 8 cobblestone blocks from that block. Upon successful completion, the bot informs about the mining event and saves it. If the wooden pickaxe is not available initially, it fails to mine cobblestones.", + "type": "comment" + }, + "380": { + "file_id": 47, + "content": "/voyager/utils/__init__.py", + "type": "filepath" + }, + "381": { + "file_id": 47, + "content": "This code imports various utility functions and classes from submodules 'file_utils', 'json_utils', and 'record_utils'. These utilities are likely to be used throughout the Voyager module for handling file operations, JSON manipulation, and event recording.", + "type": "summary" + }, + "382": { + "file_id": 47, + "content": "from .file_utils import *\nfrom .json_utils import *\nfrom .record_utils import EventRecorder", + "type": "code", + "location": "/voyager/utils/__init__.py:1-3" + }, + "383": { + "file_id": 47, + "content": "This code imports various utility functions and classes from submodules 'file_utils', 'json_utils', and 'record_utils'. These utilities are likely to be used throughout the Voyager module for handling file operations, JSON manipulation, and event recording.", + "type": "comment" + }, + "384": { + "file_id": 48, + "content": "/voyager/utils/file_utils.py", + "type": "filepath" + }, + "385": { + "file_id": 48, + "content": "This code offers utility functions for file system operations, including path manipulation and directory traversal, file/directory handling (f_remove, f_copy), wildcard support, error handling, verbose option, file paths & directories management, checksums, tarball creation with compression, utility file handling, NFS-safe lock implementation, and options to load/dump text or pickle data by lines.", + "type": "summary" + }, + "386": { + "file_id": 48, + "content": "\"\"\"\nFile system utils.\n\"\"\"\nimport collections\nimport os\nimport pickle\nimport sys\nimport errno\nimport shutil\nimport glob\n# import pwd\nimport codecs\nimport hashlib\nimport tarfile\nimport fnmatch\nimport tempfile\nfrom datetime import datetime\nfrom socket import gethostname\nimport logging\nf_ext = os.path.splitext\nf_size = os.path.getsize\nis_file = os.path.isfile\nis_dir = os.path.isdir\nget_dir = os.path.dirname\ndef host_name():\n \"Get host name, alias with ``socket.gethostname()``\"\n return gethostname()\ndef host_id():\n \"\"\"\n Returns: first part of hostname up to '.'\n \"\"\"\n return host_name().split(\".\")[0]\ndef utf_open(fname, mode):\n \"\"\"\n Wrapper for codecs.open\n \"\"\"\n return codecs.open(fname, mode=mode, encoding=\"utf-8\")\ndef is_sequence(obj):\n \"\"\"\n Returns:\n True if the sequence is a collections.Sequence and not a string.\n \"\"\"\n return isinstance(obj, collections.abc.Sequence) and not isinstance(obj, str)\ndef pack_varargs(args):\n \"\"\"\n Pack *args or a single list arg as list\n def f(*args):", + "type": "code", + "location": "/voyager/utils/file_utils.py:1-65" + }, + "387": { + "file_id": 48, + "content": "This code file contains various utility functions for working with the file system, such as getting hostname and host ID, opening files in UTF-8 format, checking if an object is a sequence, and packing variable arguments into a list. These functions can be used across different modules within the project for file operations.", + "type": "comment" + }, + "388": { + "file_id": 48, + "content": " arg_list = pack_varargs(args)\n # arg_list is now packed as a list\n \"\"\"\n assert isinstance(args, tuple), \"please input the tuple `args` as in *args\"\n if len(args) == 1 and is_sequence(args[0]):\n return args[0]\n else:\n return args\ndef f_not_empty(*fpaths):\n \"\"\"\n Returns:\n True if and only if the file exists and file size > 0\n if fpath is a dir, if and only if dir exists and has at least 1 file\n \"\"\"\n fpath = f_join(*fpaths)\n if not os.path.exists(fpath):\n return False\n if os.path.isdir(fpath):\n return len(os.listdir(fpath)) > 0\n else:\n return os.path.getsize(fpath) > 0\ndef f_expand(fpath):\n return os.path.expandvars(os.path.expanduser(fpath))\ndef f_exists(*fpaths):\n return os.path.exists(f_join(*fpaths))\ndef f_join(*fpaths):\n \"\"\"\n join file paths and expand special symbols like `~` for home dir\n \"\"\"\n fpaths = pack_varargs(fpaths)\n fpath = f_expand(os.path.join(*fpaths))\n if isinstance(fpath, str):\n fpath = fpath.strip()", + "type": "code", + "location": "/voyager/utils/file_utils.py:66-107" + }, + "389": { + "file_id": 48, + "content": "The code defines a series of utility functions for file manipulation. It includes functions like `f_not_empty` which checks if the given file exists and has a non-zero size, `f_expand` to expand special symbols in file paths, `f_exists` to check if a file path exists, and `f_join` to join file paths while expanding special symbols. The code also uses `pack_varargs` for packing arguments into a list format.", + "type": "comment" + }, + "390": { + "file_id": 48, + "content": " return fpath\ndef f_listdir(\n *fpaths,\n filter_ext=None,\n filter=None,\n sort=True,\n full_path=False,\n nonexist_ok=True,\n recursive=False,\n):\n \"\"\"\n Args:\n full_path: True to return full paths to the dir contents\n filter: function that takes in file name and returns True to include\n nonexist_ok: True to return [] if the dir is non-existent, False to raise\n sort: sort the file names by alphabetical\n recursive: True to use os.walk to recursively list files. Note that `filter`\n will be applied to the relative path string to the root dir.\n e.g. filter will take \"a/data1.txt\" and \"a/b/data3.txt\" as input, instead of\n just the base file names \"data1.txt\" and \"data3.txt\".\n if False, will simply call os.listdir()\n \"\"\"\n assert not (filter_ext and filter), \"filter_ext and filter are mutually exclusive\"\n dir_path = f_join(*fpaths)\n if not os.path.exists(dir_path) and nonexist_ok:\n return []\n if recursive:", + "type": "code", + "location": "/voyager/utils/file_utils.py:108-136" + }, + "391": { + "file_id": 48, + "content": "This function, f_listdir, takes in one or more file paths as arguments and returns a list of files in the specified directories. It can filter files based on their extension or through a user-defined function, sort the file names alphabetically, return full paths to the dir contents, handle non-existent directories gracefully, and optionally use os.walk for recursive directory traversal. However, filter_ext and filter cannot be used together.", + "type": "comment" + }, + "392": { + "file_id": 48, + "content": " files = [\n os.path.join(os.path.relpath(root, dir_path), file)\n for root, _, files in os.walk(dir_path)\n for file in files\n ]\n else:\n files = os.listdir(dir_path)\n if filter is not None:\n files = [f for f in files if filter(f)]\n elif filter_ext is not None:\n files = [f for f in files if f.endswith(filter_ext)]\n if sort:\n files.sort()\n if full_path:\n return [os.path.join(dir_path, f) for f in files]\n else:\n return files\ndef f_mkdir(*fpaths):\n \"\"\"\n Recursively creates all the subdirs\n If exist, do nothing.\n \"\"\"\n fpath = f_join(*fpaths)\n os.makedirs(fpath, exist_ok=True)\n return fpath\ndef f_mkdir_in_path(*fpaths):\n \"\"\"\n fpath is a file,\n recursively creates all the parent dirs that lead to the file\n If exist, do nothing.\n \"\"\"\n os.makedirs(get_dir(f_join(*fpaths)), exist_ok=True)\ndef last_part_in_path(fpath):\n \"\"\"\n https://stackoverflow.com/questions/3925096/how-to-get-only-the-last-part-of-a-path-in-python", + "type": "code", + "location": "/voyager/utils/file_utils.py:137-177" + }, + "393": { + "file_id": 48, + "content": "The code defines three functions: \"f_list\", \"f_mkdir\", and \"last_part_in_path\". \n\"f_list\" takes a root directory and optional filter or sort parameters, and returns a list of files in the directory. It can also return absolute paths if the 'full_path' parameter is set to True. \n\"f_mkdir\" recursively creates all subdirectories leading up to a specified file path, creating them only if they do not already exist. \n\"last_part_in_path\" returns the last part of a given file path by splitting it using '/'.", + "type": "comment" + }, + "394": { + "file_id": 48, + "content": " \"\"\"\n return os.path.basename(os.path.normpath(f_expand(fpath)))\ndef is_abs_path(*fpath):\n return os.path.isabs(f_join(*fpath))\ndef is_relative_path(*fpath):\n return not is_abs_path(f_join(*fpath))\ndef f_time(*fpath):\n \"File modification time\"\n return str(os.path.getctime(f_join(*fpath)))\ndef f_append_before_ext(fpath, suffix):\n \"\"\"\n Append a suffix to file name and retain its extension\n \"\"\"\n name, ext = f_ext(fpath)\n return name + suffix + ext\ndef f_add_ext(fpath, ext):\n \"\"\"\n Append an extension if not already there\n Args:\n ext: will add a preceding `.` if doesn't exist\n \"\"\"\n if not ext.startswith(\".\"):\n ext = \".\" + ext\n if fpath.endswith(ext):\n return fpath\n else:\n return fpath + ext\ndef f_has_ext(fpath, ext):\n \"Test if file path is a text file\"\n _, actual_ext = f_ext(fpath)\n return actual_ext == \".\" + ext.lstrip(\".\")\ndef f_glob(*fpath):\n return glob.glob(f_join(*fpath), recursive=True)\ndef f_remove(*fpath, verbose=False, dry_run=False):", + "type": "code", + "location": "/voyager/utils/file_utils.py:178-227" + }, + "395": { + "file_id": 48, + "content": "The code defines various file utility functions. It checks if a path is absolute or relative, retrieves file modification time, appends suffix to file name retaining its extension, adds an extension if not already present, tests if the file has a specific extension, and globally searches for files recursively.", + "type": "comment" + }, + "396": { + "file_id": 48, + "content": " \"\"\"\n If exist, remove. Supports both dir and file. Supports glob wildcard.\n \"\"\"\n assert isinstance(verbose, bool)\n fpath = f_join(fpath)\n if dry_run:\n print(\"Dry run, delete:\", fpath)\n return\n for f in glob.glob(fpath):\n try:\n shutil.rmtree(f)\n except OSError as e:\n if e.errno == errno.ENOTDIR:\n try:\n os.remove(f)\n except: # final resort safeguard\n pass\n if verbose:\n print(f'Deleted \"{fpath}\"')\ndef f_copy(fsrc, fdst, ignore=None, include=None, exists_ok=True, verbose=False):\n \"\"\"\n Supports both dir and file. Supports glob wildcard.\n \"\"\"\n fsrc, fdst = f_expand(fsrc), f_expand(fdst)\n for f in glob.glob(fsrc):\n try:\n f_copytree(f, fdst, ignore=ignore, include=include, exist_ok=exists_ok)\n except OSError as e:\n if e.errno == errno.ENOTDIR:\n shutil.copy(f, fdst)\n else:\n raise\n if verbose:", + "type": "code", + "location": "/voyager/utils/file_utils.py:228-262" + }, + "397": { + "file_id": 48, + "content": "The code defines two functions: `f_remove` and `f_copy`. `f_remove` removes a file or directory specified by the given path, supporting wildcards. It prints a dry-run message if in that mode, and handles different OSError scenarios to remove either directories or files. `f_copy` copies files or directories from one location to another, supporting wildcards and ignoring/including certain files as specified by `ignore` and `include`. If any OSError occurs while copying a directory, it attempts to copy the file instead. If verbose is True, it prints success messages for each deletion or copy action.", + "type": "comment" + }, + "398": { + "file_id": 48, + "content": " print(f'Copied \"{fsrc}\" to \"{fdst}\"')\ndef _f_copytree(\n src,\n dst,\n symlinks=False,\n ignore=None,\n exist_ok=True,\n copy_function=shutil.copy2,\n ignore_dangling_symlinks=False,\n):\n \"\"\"Copied from python standard lib shutil.copytree\n except that we allow exist_ok\n Use f_copytree as entry\n \"\"\"\n names = os.listdir(src)\n if ignore is not None:\n ignored_names = ignore(src, names)\n else:\n ignored_names = set()\n os.makedirs(dst, exist_ok=exist_ok)\n errors = []\n for name in names:\n if name in ignored_names:\n continue\n srcname = os.path.join(src, name)\n dstname = os.path.join(dst, name)\n try:\n if os.path.islink(srcname):\n linkto = os.readlink(srcname)\n if symlinks:\n # We can't just leave it to `copy_function` because legacy\n # code with a custom `copy_function` may rely on copytree\n # doing the right thing.\n os.symlink(linkto, dstname)", + "type": "code", + "location": "/voyager/utils/file_utils.py:263-299" + }, + "399": { + "file_id": 48, + "content": "This code defines a function called `_f_copytree` that copies files and directories from one location to another, allowing for existing destination files and directories if the `exist_ok` parameter is set to True. It also includes functionality to handle symbolic links, ignoring certain names specified by an optional `ignore` function, and using the `os`, `shutil`, and `path` modules.", + "type": "comment" + } +} \ No newline at end of file diff --git a/docs/data/4.json b/docs/data/4.json new file mode 100644 index 00000000..2b6edab8 --- /dev/null +++ b/docs/data/4.json @@ -0,0 +1,439 @@ +{ + "400": { + "file_id": 48, + "content": " shutil.copystat(srcname, dstname, follow_symlinks=not symlinks)\n else:\n # ignore dangling symlink if the flag is on\n if not os.path.exists(linkto) and ignore_dangling_symlinks:\n continue\n # otherwise let the copy occurs. copy2 will raise an error\n if os.path.isdir(srcname):\n _f_copytree(\n srcname, dstname, symlinks, ignore, exist_ok, copy_function\n )\n else:\n copy_function(srcname, dstname)\n elif os.path.isdir(srcname):\n _f_copytree(srcname, dstname, symlinks, ignore, exist_ok, copy_function)\n else:\n # Will raise a SpecialFileError for unsupported file types\n copy_function(srcname, dstname)\n # catch the Error from the recursive copytree so that we can\n # continue with other files\n except shutil.Error as err:", + "type": "code", + "location": "/voyager/utils/file_utils.py:300-319" + }, + "401": { + "file_id": 48, + "content": "This code handles copying files and directories between two locations. If symlinks are ignored, it uses shutil.copystat to preserve file permissions. If a dangling symlink is encountered and ignore_dangling_symlinks flag is on, the code continues to the next file or directory. It uses _f_copytree for copying directories recursively and calls copy_function for files. Errors are caught and handled to continue with other files.", + "type": "comment" + }, + "402": { + "file_id": 48, + "content": " errors.extend(err.args[0])\n except OSError as why:\n errors.append((srcname, dstname, str(why)))\n try:\n shutil.copystat(src, dst)\n except OSError as why:\n # Copying file access times may fail on Windows\n if getattr(why, \"winerror\", None) is None:\n errors.append((src, dst, str(why)))\n if errors:\n raise shutil.Error(errors)\n return dst\ndef _include_patterns(*patterns):\n \"\"\"Factory function that can be used with copytree() ignore parameter.\n Arguments define a sequence of glob-style patterns\n that are used to specify what files to NOT ignore.\n Creates and returns a function that determines this for each directory\n in the file hierarchy rooted at the source directory when used with\n shutil.copytree().\n \"\"\"\n def _ignore_patterns(path, names):\n keep = set(\n name for pattern in patterns for name in fnmatch.filter(names, pattern)\n )\n ignore = set(\n name\n for name in names", + "type": "code", + "location": "/voyager/utils/file_utils.py:320-350" + }, + "403": { + "file_id": 48, + "content": "The code defines a function _include_patterns that creates and returns a function _ignore_patterns to be used with shutil.copytree(). It takes glob-style patterns as arguments, and determines what files to NOT ignore in the file hierarchy rooted at the source directory when copying using shutil.copystat(). Errors during file operations are handled by raising an Error if any occur.", + "type": "comment" + }, + "404": { + "file_id": 48, + "content": " if name not in keep and not os.path.isdir(os.path.join(path, name))\n )\n return ignore\n return _ignore_patterns\ndef f_copytree(fsrc, fdst, symlinks=False, ignore=None, include=None, exist_ok=True):\n fsrc, fdst = f_expand(fsrc), f_expand(fdst)\n assert (ignore is None) or (\n include is None\n ), \"ignore= and include= are mutually exclusive\"\n if ignore:\n ignore = shutil.ignore_patterns(*ignore)\n elif include:\n ignore = _include_patterns(*include)\n _f_copytree(fsrc, fdst, ignore=ignore, symlinks=symlinks, exist_ok=exist_ok)\ndef f_move(fsrc, fdst):\n fsrc, fdst = f_expand(fsrc), f_expand(fdst)\n for f in glob.glob(fsrc):\n shutil.move(f, fdst)\ndef f_split_path(fpath, normpath=True):\n \"\"\"\n Splits path into a list of its component folders\n Args:\n normpath: call os.path.normpath to remove redundant '/' and\n up-level references like \"..\"\n \"\"\"\n if normpath:\n fpath = os.path.normpath(fpath)\n allparts = []\n while 1:", + "type": "code", + "location": "/voyager/utils/file_utils.py:351-387" + }, + "405": { + "file_id": 48, + "content": "This code includes functions for copying, moving and splitting file paths. The `f_copytree` function copies a source directory to a destination, ignoring certain patterns. The `f_move` function moves files from one location to another. Lastly, the `f_split_path` function splits a file path into a list of its component folders.", + "type": "comment" + }, + "406": { + "file_id": 48, + "content": " parts = os.path.split(fpath)\n if parts[0] == fpath: # sentinel for absolute paths\n allparts.insert(0, parts[0])\n break\n elif parts[1] == fpath: # sentinel for relative paths\n allparts.insert(0, parts[1])\n break\n else:\n fpath = parts[0]\n allparts.insert(0, parts[1])\n return allparts\ndef get_script_dir():\n \"\"\"\n Returns: the dir of current script\n \"\"\"\n return os.path.dirname(os.path.realpath(sys.argv[0]))\ndef get_script_file_name():\n \"\"\"\n Returns: the dir of current script\n \"\"\"\n return os.path.basename(sys.argv[0])\ndef get_script_self_path():\n \"\"\"\n Returns: the dir of current script\n \"\"\"\n return os.path.realpath(sys.argv[0])\ndef get_parent_dir(location, abspath=False):\n \"\"\"\n Args:\n location: current directory or file\n Returns:\n parent directory absolute or relative path\n \"\"\"\n _path = os.path.abspath if abspath else os.path.relpath\n return _path(f_join(location, os.pardir))", + "type": "code", + "location": "/voyager/utils/file_utils.py:388-431" + }, + "407": { + "file_id": 48, + "content": "This code defines several functions to deal with file paths and directories. The `get_allparts()` function splits a file path into its directory and base name, handling both absolute and relative paths. The `get_script_dir()`, `get_script_file_name()`, and `get_script_self_path()` functions return the directory, filename, and absolute path of the current script, respectively. Lastly, the `get_parent_dir()` function takes a location (directory or file) and returns its parent directory's path, either relative or absolute depending on the optional argument.", + "type": "comment" + }, + "408": { + "file_id": 48, + "content": "def md5_checksum(*fpath):\n \"\"\"\n File md5 signature\n \"\"\"\n hash_md5 = hashlib.md5()\n with open(f_join(*fpath), \"rb\") as f:\n for chunk in iter(lambda: f.read(65536), b\"\"):\n hash_md5.update(chunk)\n return hash_md5.hexdigest()\ndef create_tar(fsrc, output_tarball, include=None, ignore=None, compress_mode=\"gz\"):\n \"\"\"\n Args:\n fsrc: source file or folder\n output_tarball: output tar file name\n compress_mode: \"gz\", \"bz2\", \"xz\" or \"\" (empty for uncompressed write)\n include: include pattern, will trigger copy to temp directory\n ignore: ignore pattern, will trigger copy to temp directory\n \"\"\"\n fsrc, output_tarball = f_expand(fsrc), f_expand(output_tarball)\n assert compress_mode in [\"gz\", \"bz2\", \"xz\", \"\"]\n src_base = os.path.basename(fsrc)\n tempdir = None\n if include or ignore:\n tempdir = tempfile.mkdtemp()\n tempdest = f_join(tempdir, src_base)\n f_copy(fsrc, tempdest, include=include, ignore=ignore)\n fsrc = tempdest", + "type": "code", + "location": "/voyager/utils/file_utils.py:434-463" + }, + "409": { + "file_id": 48, + "content": "The code provides functions for calculating an MD5 checksum of a file and creating a tarball. The `md5_checksum` function takes a file path as input and returns the MD5 hash of its contents in hexdigest format. The `create_tar` function creates a tarball from either a source file or folder, optionally compressing it with gzip, bzip2, or xz, and allows for including or excluding specific files based on patterns. If any include or ignore patterns are provided, the source files are temporarily copied to a new directory before being compressed into the tarball.", + "type": "comment" + }, + "410": { + "file_id": 48, + "content": " with tarfile.open(output_tarball, \"w:\" + compress_mode) as tar:\n tar.add(fsrc, arcname=src_base)\n if tempdir:\n f_remove(tempdir)\ndef extract_tar(source_tarball, output_dir=\".\", members=None):\n \"\"\"\n Args:\n source_tarball: extract members from archive\n output_dir: default to current working dir\n members: must be a subset of the list returned by getmembers()\n \"\"\"\n source_tarball, output_dir = f_expand(source_tarball), f_expand(output_dir)\n with tarfile.open(source_tarball, \"r:*\") as tar:\n tar.extractall(output_dir, members=members)\ndef move_with_backup(*fpath, suffix=\".bak\"):\n \"\"\"\n Ensures that a path is not occupied. If there is a file, rename it by\n adding @suffix. Resursively backs up everything.\n Args:\n fpath: file path to clear\n suffix: Add to backed up files (default: {'.bak'})\n \"\"\"\n fpath = str(f_join(*fpath))\n if os.path.exists(fpath):\n move_with_backup(fpath + suffix)\n shutil.move(fpath, fpath + suffix)", + "type": "code", + "location": "/voyager/utils/file_utils.py:465-496" + }, + "411": { + "file_id": 48, + "content": "Code snippet is from Voyager project and contains three functions: 'create_tar', 'extract_tar' and 'move_with_backup'.\n\n1. 'create_tar' creates a tar archive with the option to specify compression mode, source path, and the base name for the archive. It also removes the temporary directory if it exists.\n2. 'extract_tar' extracts members from the specified tar archive into a destination directory, allowing specification of members to extract.\n3. 'move_with_backup' ensures that a file or path is not occupied by renaming existing files with a suffix and recursively backs up everything in the directory.", + "type": "comment" + }, + "412": { + "file_id": 48, + "content": "def insert_before_ext(name, insert):\n \"\"\"\n log.txt -> log.ep50.txt\n \"\"\"\n name, ext = os.path.splitext(name)\n return name + insert + ext\ndef timestamp_file_name(fname):\n timestr = datetime.now().strftime(\"_%H-%M-%S_%m-%d-%y\")\n return insert_before_ext(fname, timestr)\ndef get_file_lock(*fpath, timeout: int = 15, logging_level=\"critical\"):\n \"\"\"\n NFS-safe filesystem-backed lock. `pip install flufl.lock`\n https://flufllock.readthedocs.io/en/stable/apiref.html\n Args:\n fpath: should be a path on NFS so that every process can see it\n timeout: seconds\n \"\"\"\n from flufl.lock import Lock\n logging.getLogger(\"flufl.lock\").setLevel(logging_level.upper())\n return Lock(f_join(*fpath), lifetime=timeout)\ndef load_pickle(*fpaths):\n with open(f_join(*fpaths), \"rb\") as fp:\n return pickle.load(fp)\ndef dump_pickle(data, *fpaths):\n with open(f_join(*fpaths), \"wb\") as fp:\n pickle.dump(data, fp)\ndef load_text(*fpaths, by_lines=False):\n with open(f_join(*fpaths), \"r\") as fp:", + "type": "code", + "location": "/voyager/utils/file_utils.py:499-538" + }, + "413": { + "file_id": 48, + "content": "This code contains several utility functions for file operations, including inserting a string before the extension of a file name, generating timestamp-based file names, implementing a NFS-safe file lock using flufl.lock, loading and dumping pickles, and loading text files. The code also includes some logging setup for the flufl.lock module.", + "type": "comment" + }, + "414": { + "file_id": 48, + "content": " if by_lines:\n return fp.readlines()\n else:\n return fp.read()\ndef load_text_lines(*fpaths):\n return load_text(*fpaths, by_lines=True)\ndef dump_text(s, *fpaths):\n with open(f_join(*fpaths), \"w\") as fp:\n fp.write(s)\ndef dump_text_lines(lines: list[str], *fpaths, add_newline=True):\n with open(f_join(*fpaths), \"w\") as fp:\n for line in lines:\n print(line, file=fp, end=\"\\n\" if add_newline else \"\")\n# aliases to be consistent with other load_* and dump_*\npickle_load = load_pickle\npickle_dump = dump_pickle\ntext_load = load_text\nread_text = load_text\nread_text_lines = load_text_lines\nwrite_text = dump_text\nwrite_text_lines = dump_text_lines\ntext_dump = dump_text", + "type": "code", + "location": "/voyager/utils/file_utils.py:539-568" + }, + "415": { + "file_id": 48, + "content": "The code defines various functions for loading and dumping text or pickle data, providing options to read by lines or not. It also includes alias names to maintain consistency with other load_* and dump_* functions in the codebase.", + "type": "comment" + }, + "416": { + "file_id": 49, + "content": "/voyager/utils/json_utils.py", + "type": "filepath" + }, + "417": { + "file_id": 49, + "content": "This code offers JSON handling functions, correcting escapes and braces count imbalance using aliases. It removes non-JSON content before validating with Python's json.loads().", + "type": "summary" + }, + "418": { + "file_id": 49, + "content": "import json\nimport re\nfrom typing import Any, Dict, Union\nfrom .file_utils import f_join\ndef json_load(*file_path, **kwargs):\n file_path = f_join(file_path)\n with open(file_path, \"r\") as fp:\n return json.load(fp, **kwargs)\ndef json_loads(string, **kwargs):\n return json.loads(string, **kwargs)\ndef json_dump(data, *file_path, **kwargs):\n file_path = f_join(file_path)\n with open(file_path, \"w\") as fp:\n json.dump(data, fp, **kwargs)\ndef json_dumps(data, **kwargs):\n \"\"\"\n Returns: string\n \"\"\"\n return json.dumps(data, **kwargs)\n# ---------------- Aliases -----------------\n# add aliases where verb goes first, json_load -> load_json\nload_json = json_load\nloads_json = json_loads\ndump_json = json_dump\ndumps_json = json_dumps\ndef extract_char_position(error_message: str) -> int:\n \"\"\"Extract the character position from the JSONDecodeError message.\n Args:\n error_message (str): The error message from the JSONDecodeError\n exception.\n Returns:\n int: The character position.", + "type": "code", + "location": "/voyager/utils/json_utils.py:1-44" + }, + "419": { + "file_id": 49, + "content": "The code defines various functions for handling JSON data: `json_load`, `json_loads`, `json_dump`, and `json_dumps`. It also includes aliases for these functions prefixed with \"load_\", \"dump_\", and \"dumps_\". The function `extract_char_position` extracts the character position from a JSONDecodeError error message.", + "type": "comment" + }, + "420": { + "file_id": 49, + "content": " \"\"\"\n import re\n char_pattern = re.compile(r\"\\(char (\\d+)\\)\")\n if match := char_pattern.search(error_message):\n return int(match[1])\n else:\n raise ValueError(\"Character position not found in the error message.\")\ndef add_quotes_to_property_names(json_string: str) -> str:\n \"\"\"\n Add quotes to property names in a JSON string.\n Args:\n json_string (str): The JSON string.\n Returns:\n str: The JSON string with quotes added to property names.\n \"\"\"\n def replace_func(match):\n return f'\"{match.group(1)}\":'\n property_name_pattern = re.compile(r\"(\\w+):\")\n corrected_json_string = property_name_pattern.sub(replace_func, json_string)\n try:\n json.loads(corrected_json_string)\n return corrected_json_string\n except json.JSONDecodeError as e:\n raise e\ndef balance_braces(json_string: str) -> str:\n \"\"\"\n Balance the braces in a JSON string.\n Args:\n json_string (str): The JSON string.\n Returns:\n str: The JSON string with braces balanced.", + "type": "code", + "location": "/voyager/utils/json_utils.py:45-83" + }, + "421": { + "file_id": 49, + "content": "Code snippet includes three functions: \n1. `char_position()` - searches for character position in error message using regex and returns the integer value.\n2. `add_quotes_to_property_names()` - adds quotes to property names in JSON string, ensuring valid syntax.\n3. `balance_braces()` - balances braces in a JSON string to ensure proper syntax.", + "type": "comment" + }, + "422": { + "file_id": 49, + "content": " \"\"\"\n open_braces_count = json_string.count(\"{\")\n close_braces_count = json_string.count(\"}\")\n while open_braces_count > close_braces_count:\n json_string += \"}\"\n close_braces_count += 1\n while close_braces_count > open_braces_count:\n json_string = json_string.rstrip(\"}\")\n close_braces_count -= 1\n try:\n json.loads(json_string)\n return json_string\n except json.JSONDecodeError as e:\n raise e\ndef fix_invalid_escape(json_str: str, error_message: str) -> str:\n while error_message.startswith(\"Invalid \\\\escape\"):\n bad_escape_location = extract_char_position(error_message)\n json_str = json_str[:bad_escape_location] + json_str[bad_escape_location + 1 :]\n try:\n json.loads(json_str)\n return json_str\n except json.JSONDecodeError as e:\n error_message = str(e)\n return json_str\ndef correct_json(json_str: str) -> str:\n \"\"\"\n Correct common JSON errors.\n Args:\n json_str (str): The JSON string.", + "type": "code", + "location": "/voyager/utils/json_utils.py:84-120" + }, + "423": { + "file_id": 49, + "content": "This code fixes common JSON errors by adding missing closing braces, removing invalid escapes and ensures the corrected string is valid JSON. The function \"fix_invalid_escape\" handles removing invalid escape characters, \"correct_json\" adjusts closing brace count imbalance and \"corrected_json_string\" verifies if the string is a valid JSON.", + "type": "comment" + }, + "424": { + "file_id": 49, + "content": " \"\"\"\n try:\n json.loads(json_str)\n return json_str\n except json.JSONDecodeError as e:\n error_message = str(e)\n if error_message.startswith(\"Invalid \\\\escape\"):\n json_str = fix_invalid_escape(json_str, error_message)\n if error_message.startswith(\n \"Expecting property name enclosed in double quotes\"\n ):\n json_str = add_quotes_to_property_names(json_str)\n try:\n json.loads(json_str)\n return json_str\n except json.JSONDecodeError as e:\n error_message = str(e)\n if balanced_str := balance_braces(json_str):\n return balanced_str\n return json_str\ndef fix_and_parse_json(\n json_str: str, try_to_fix_with_gpt: bool = True\n) -> Union[str, Dict[Any, Any]]:\n \"\"\"Fix and parse JSON string\"\"\"\n try:\n json_str = json_str.replace(\"\\t\", \"\")\n return json.loads(json_str)\n except json.JSONDecodeError as _: # noqa: F841\n json_str = correct_json(json_str)", + "type": "code", + "location": "/voyager/utils/json_utils.py:121-152" + }, + "425": { + "file_id": 49, + "content": "This code attempts to fix and parse a potentially invalid JSON string. It first tries to load the string using `json.loads()`. If this fails with a `JSONDecodeError`, it checks the error message for specific issues like escape characters or missing quotes, and applies fixes accordingly before retrying to load the string as JSON. If none of these methods succeed, the original JSON string is returned. The function also includes an optional flag to try to fix the string using GPT (Generic Programming Tool), but this feature seems to be unimplemented in the provided code.", + "type": "comment" + }, + "426": { + "file_id": 49, + "content": " try:\n return json.loads(json_str)\n except json.JSONDecodeError as _: # noqa: F841\n pass\n # Let's do something manually:\n # sometimes GPT responds with something BEFORE the braces:\n # \"I'm sorry, I don't understand. Please try again.\"\n # {\"text\": \"I'm sorry, I don't understand. Please try again.\",\n # \"confidence\": 0.0}\n # So let's try to find the first brace and then parse the rest\n # of the string\n try:\n brace_index = json_str.index(\"{\")\n json_str = json_str[brace_index:]\n last_brace_index = json_str.rindex(\"}\")\n json_str = json_str[: last_brace_index + 1]\n return json.loads(json_str)\n except json.JSONDecodeError as e: # noqa: F841\n # if try_to_fix_with_gpt:\n # print(\n # \"Warning: Failed to parse AI output, attempting to fix.\"\n # \"\\n If you see this warning frequently, it's likely that\"\n # \" your prompt is confusing the AI. Try changing it up\"\n # \" slightly.\"", + "type": "code", + "location": "/voyager/utils/json_utils.py:153-176" + }, + "427": { + "file_id": 49, + "content": "This code attempts to handle JSON strings where the AI response may contain text before the opening brace. It first tries to parse the original string using json.loads(), then, if an error occurs, it finds the first opening brace, removes any non-JSON text before it, and parses the remaining string as JSON.", + "type": "comment" + }, + "428": { + "file_id": 49, + "content": " # )\n # # Now try to fix this up using the ai_functions\n # ai_fixed_json = fix_json(json_str, JSON_SCHEMA)\n #\n # if ai_fixed_json != \"failed\":\n # return json.loads(ai_fixed_json)\n # else:\n # # This allows the AI to react to the error message,\n # # which usually results in it correcting its ways.\n # print(\"Failed to fix ai output, telling the AI.\")\n # return json_str\n # else:\n raise e\n# def fix_json(json_str: str, schema: str) -> str:\n# \"\"\"Fix the given JSON string to make it parseable and fully complient with the provided schema.\"\"\"\n#\n# # Try to fix the JSON using gpt:\n# function_string = \"def fix_json(json_str: str, schema:str=None) -> str:\"\n# args = [f\"'''{json_str}'''\", f\"'''{schema}'''\"]\n# description_string = (\n# \"Fixes the provided JSON string to make it parseable\"\n# \" and fully complient with the provided schema.\\n If an object or\"", + "type": "code", + "location": "/voyager/utils/json_utils.py:177-200" + }, + "429": { + "file_id": 49, + "content": "The code attempts to fix a JSON string by using the function `fix_json` with a provided schema. If the AI-fixed JSON is not \"failed\", it will be loaded using `json.loads`. If the fix fails, it prints a message and returns the original JSON string. The function utilizes an AI to correct the JSON string according to the given schema.", + "type": "comment" + }, + "430": { + "file_id": 49, + "content": "# \" field specified in the schema isn't contained within the correct\"\n# \" JSON, it is ommited.\\n This function is brilliant at guessing\"\n# \" when the format is incorrect.\"\n# )\n#\n# # If it doesn't already start with a \"`\", add one:\n# if not json_str.startswith(\"`\"):\n# json_str = \"```json\\n\" + json_str + \"\\n```\"\n# result_string = call_ai_function(\n# function_string, args, description_string, model=cfg.fast_llm_model\n# )\n# if cfg.debug:\n# print(\"------------ JSON FIX ATTEMPT ---------------\")\n# print(f\"Original JSON: {json_str}\")\n# print(\"-----------\")\n# print(f\"Fixed JSON: {result_string}\")\n# print(\"----------- END OF FIX ATTEMPT ----------------\")\n#\n# try:\n# json.loads(result_string) # just check the validity\n# return result_string\n# except: # noqa: E722\n# # Get the call stack:\n# # import traceback\n# # call_stack = traceback.format_exc()\n# # print(f\"Failed to fix JSON: '{json_str}' \"+call_stack)", + "type": "code", + "location": "/voyager/utils/json_utils.py:201-226" + }, + "431": { + "file_id": 49, + "content": "This code is attempting to fix incorrect JSON format by enclosing it within triple backticks, and then validating the corrected JSON using Python's json.loads() method. If the JSON remains invalid, an error will be raised. This function is designed to handle cases where the input JSON has minor formatting issues.", + "type": "comment" + }, + "432": { + "file_id": 49, + "content": "# return \"failed\"", + "type": "code", + "location": "/voyager/utils/json_utils.py:227-227" + }, + "433": { + "file_id": 49, + "content": "This line of code is conditionally returning the string \"failed\" as a result. It's likely part of an if statement or function that evaluates some input and assigns this value based on specific criteria not shown in this snippet.", + "type": "comment" + }, + "434": { + "file_id": 50, + "content": "/voyager/utils/record_utils.py", + "type": "filepath" + }, + "435": { + "file_id": 50, + "content": "The EventRecorder class records events, updates elapsed time and item history, and maintains dictionaries for item-time associations and iteration items. It has a resume method and sorts records based on timestamps.", + "type": "summary" + }, + "436": { + "file_id": 50, + "content": "import time\nfrom .file_utils import *\nfrom .json_utils import *\nclass EventRecorder:\n def __init__(\n self,\n ckpt_dir=\"ckpt\",\n resume=False,\n init_position=None,\n ):\n self.ckpt_dir = ckpt_dir\n self.item_history = set()\n self.item_vs_time = {}\n self.item_vs_iter = {}\n self.biome_history = set()\n self.init_position = init_position\n self.position_history = [[0, 0]]\n self.elapsed_time = 0\n self.iteration = 0\n f_mkdir(self.ckpt_dir, \"events\")\n if resume:\n self.resume()\n def record(self, events, task):\n task = re.sub(r'[\\\\/:\"*?<>| ]', \"_\", task)\n task = task.replace(\" \", \"_\") + time.strftime(\n \"_%Y%m%d_%H%M%S\", time.localtime()\n )\n self.iteration += 1\n if not self.init_position:\n self.init_position = [\n events[0][1][\"status\"][\"position\"][\"x\"],\n events[0][1][\"status\"][\"position\"][\"z\"],\n ]\n for event_type, event in events:", + "type": "code", + "location": "/voyager/utils/record_utils.py:1-38" + }, + "437": { + "file_id": 50, + "content": "Class EventRecorder initializes attributes, sets up a directory for event files, and handles resuming or starting fresh. It records events for tasks, storing information about each event type in various dictionaries for later use.", + "type": "comment" + }, + "438": { + "file_id": 50, + "content": " self.update_items(event)\n if event_type == \"observe\":\n self.update_elapsed_time(event)\n print(\n f\"\\033[96m****Recorder message: {self.elapsed_time} ticks have elapsed****\\033[0m\\n\"\n f\"\\033[96m****Recorder message: {self.iteration} iteration passed****\\033[0m\"\n )\n dump_json(events, f_join(self.ckpt_dir, \"events\", task))\n def resume(self, cutoff=None):\n self.item_history = set()\n self.item_vs_time = {}\n self.item_vs_iter = {}\n self.elapsed_time = 0\n self.position_history = [[0, 0]]\n def get_timestamp(string):\n timestamp = \"_\".join(string.split(\"_\")[-2:])\n return time.mktime(time.strptime(timestamp, \"%Y%m%d_%H%M%S\"))\n records = f_listdir(self.ckpt_dir, \"events\")\n sorted_records = sorted(records, key=get_timestamp)\n for record in sorted_records:\n self.iteration += 1\n if cutoff and self.iteration > cutoff:\n break", + "type": "code", + "location": "/voyager/utils/record_utils.py:39-64" + }, + "439": { + "file_id": 50, + "content": "The code is part of a Recorder class responsible for recording events. It updates elapsed time and iteration, prints messages, dumps JSON data to disk, and provides a resume method to continue from a specific cutoff point. The get_timestamp function helps sort the records based on timestamps.", + "type": "comment" + }, + "440": { + "file_id": 50, + "content": " events = load_json(f_join(self.ckpt_dir, \"events\", record))\n if not self.init_position:\n self.init_position = (\n events[0][1][\"status\"][\"position\"][\"x\"],\n events[0][1][\"status\"][\"position\"][\"z\"],\n )\n for event_type, event in events:\n self.update_items(event)\n self.update_position(event)\n if event_type == \"observe\":\n self.update_elapsed_time(event)\n def update_items(self, event):\n inventory = event[\"inventory\"]\n elapsed_time = event[\"status\"][\"elapsedTime\"]\n biome = event[\"status\"][\"biome\"]\n items = set(inventory.keys())\n new_items = items - self.item_history\n self.item_history.update(items)\n self.biome_history.add(biome)\n if new_items:\n if self.elapsed_time + elapsed_time not in self.item_vs_time:\n self.item_vs_time[self.elapsed_time + elapsed_time] = []\n self.item_vs_time[self.elapsed_time + elapsed_time].extend(new_items)", + "type": "code", + "location": "/voyager/utils/record_utils.py:65-88" + }, + "441": { + "file_id": 50, + "content": "This code loads event data and updates the position, items, and elapsed time based on each event type. It also maintains a history of items and biomes encountered over time. New items are added to the item_vs_time dictionary, which tracks when they were first encountered.", + "type": "comment" + }, + "442": { + "file_id": 50, + "content": " if self.iteration not in self.item_vs_iter:\n self.item_vs_iter[self.iteration] = []\n self.item_vs_iter[self.iteration].extend(new_items)\n def update_elapsed_time(self, event):\n self.elapsed_time += event[\"status\"][\"elapsedTime\"]\n def update_position(self, event):\n position = [\n event[\"status\"][\"position\"][\"x\"] - self.init_position[0],\n event[\"status\"][\"position\"][\"z\"] - self.init_position[1],\n ]\n if self.position_history[-1] != position:\n self.position_history.append(position)", + "type": "code", + "location": "/voyager/utils/record_utils.py:89-102" + }, + "443": { + "file_id": 50, + "content": "The `update_position` function checks if the position has changed and adds it to the `position_history` list. The `update_elapsed_time` function increments the elapsed time with the event's elapsed time. The `item_vs_iter` dictionary stores the items associated with each iteration, and if a new iteration is encountered, it initializes an empty list for that iteration in the dictionary.", + "type": "comment" + }, + "444": { + "file_id": 51, + "content": "/voyager/voyager.py", + "type": "filepath" + }, + "445": { + "file_id": 51, + "content": "The Voyager class handles AI communication, environment setup, agent creation, rollout configuration, action updates, messaging, result recording, exception management, and prints tasks completion/failure with ANSI coloring.", + "type": "summary" + }, + "446": { + "file_id": 51, + "content": "import copy\nimport json\nimport os\nimport time\nfrom typing import Dict\nimport voyager.utils as U\nfrom .env import VoyagerEnv\nfrom .agents import ActionAgent\nfrom .agents import CriticAgent\nfrom .agents import CurriculumAgent\nfrom .agents import SkillManager\n# TODO: remove event memory\nclass Voyager:\n def __init__(\n self,\n mc_port: int = None,\n azure_login: Dict[str, str] = None,\n server_port: int = 3000,\n openai_api_key: str = None,\n env_wait_ticks: int = 20,\n env_request_timeout: int = 600,\n max_iterations: int = 160,\n reset_placed_if_failed: bool = False,\n action_agent_model_name: str = \"gpt-4\",\n action_agent_temperature: float = 0,\n action_agent_task_max_retries: int = 4,\n action_agent_show_chat_log: bool = True,\n action_agent_show_execution_error: bool = True,\n curriculum_agent_model_name: str = \"gpt-4\",\n curriculum_agent_temperature: float = 0,\n curriculum_agent_qa_model_name: str = \"gpt-3.5-turbo\",", + "type": "code", + "location": "/voyager/voyager.py:1-35" + }, + "447": { + "file_id": 51, + "content": "The code defines a class called \"Voyager\" that serves as the main module for an AI agent. It includes various attributes and functions related to the environment, agents (ActionAgent, CriticAgent, CurriculumAgent), SkillManager, and other settings such as server and model configurations. The class's purpose is to handle communication between the user, agent, and environment, and manage agent learning and decision-making processes.", + "type": "comment" + }, + "448": { + "file_id": 51, + "content": " curriculum_agent_qa_temperature: float = 0,\n curriculum_agent_warm_up: Dict[str, int] = None,\n curriculum_agent_core_inventory_items: str = r\".*_log|.*_planks|stick|crafting_table|furnace\"\n r\"|cobblestone|dirt|coal|.*_pickaxe|.*_sword|.*_axe\",\n curriculum_agent_mode: str = \"auto\",\n critic_agent_model_name: str = \"gpt-4\",\n critic_agent_temperature: float = 0,\n critic_agent_mode: str = \"auto\",\n skill_manager_model_name: str = \"gpt-3.5-turbo\",\n skill_manager_temperature: float = 0,\n skill_manager_retrieval_top_k: int = 5,\n openai_api_request_timeout: int = 240,\n ckpt_dir: str = \"ckpt\",\n skill_library_dir: str = None,\n resume: bool = False,\n ):\n \"\"\"\n The main class for Voyager.\n Action agent is the iterative prompting mechanism in paper.\n Curriculum agent is the automatic curriculum in paper.\n Critic agent is the self-verification in paper.\n Skill manager is the skill library in paper.", + "type": "code", + "location": "/voyager/voyager.py:36-57" + }, + "449": { + "file_id": 51, + "content": "The code defines a class for Voyager with various parameters. The action agent is for iterative prompting, the curriculum agent for automatic curriculum, critic agent for self-verification, and skill manager for skill library as per the paper.", + "type": "comment" + }, + "450": { + "file_id": 51, + "content": " :param mc_port: minecraft in-game port\n :param azure_login: minecraft login config\n :param server_port: mineflayer port\n :param openai_api_key: openai api key\n :param env_wait_ticks: how many ticks at the end each step will wait, if you found some chat log missing,\n you should increase this value\n :param env_request_timeout: how many seconds to wait for each step, if the code execution exceeds this time,\n python side will terminate the connection and need to be resumed\n :param reset_placed_if_failed: whether to reset placed blocks if failed, useful for building task\n :param action_agent_model_name: action agent model name\n :param action_agent_temperature: action agent temperature\n :param action_agent_task_max_retries: how many times to retry if failed\n :param curriculum_agent_model_name: curriculum agent model name\n :param curriculum_agent_temperature: curriculum agent temperature\n :param curriculum_agent_qa_model_name: curriculum agent qa model name", + "type": "code", + "location": "/voyager/voyager.py:58-72" + }, + "451": { + "file_id": 51, + "content": "This code defines function parameters for a Minecraft bot. Parameters include the minecraft port, Azure login credentials, Mineflayer port, OpenAI API key, wait time for each step, timeout for execution, reset option, and model names/temperatures for action and curriculum agents.", + "type": "comment" + }, + "452": { + "file_id": 51, + "content": " :param curriculum_agent_qa_temperature: curriculum agent qa temperature\n :param curriculum_agent_warm_up: info will show in curriculum human message\n if completed task larger than the value in dict, available keys are:\n {\n \"context\": int,\n \"biome\": int,\n \"time\": int,\n \"other_blocks\": int,\n \"nearby_entities\": int,\n \"health\": int,\n \"hunger\": int,\n \"position\": int,\n \"equipment\": int,\n \"chests\": int,\n \"optional_inventory_items\": int,\n }\n :param curriculum_agent_core_inventory_items: only show these items in inventory before optional_inventory_items\n reached in warm up\n :param curriculum_agent_mode: \"auto\" for automatic curriculum, \"manual\" for human curriculum\n :param critic_agent_model_name: critic agent model name\n :param critic_agent_temperature: critic agent temperature\n :param critic_agent_mode: \"auto\" for automatic critic ,\"manual\" for human critic", + "type": "code", + "location": "/voyager/voyager.py:73-94" + }, + "453": { + "file_id": 51, + "content": "This code is defining the parameters for creating a curriculum and setting up agents. The curriculum_agent has temperature, warm-up, and mode parameters while the critic_agent has its own temperature and mode. It also provides available keys for different game attributes like context, biome, time, etc. The \"reached in warm up\" statement suggests that some items will only appear later in the learning process.", + "type": "comment" + }, + "454": { + "file_id": 51, + "content": " :param skill_manager_model_name: skill manager model name\n :param skill_manager_temperature: skill manager temperature\n :param skill_manager_retrieval_top_k: how many skills to retrieve for each task\n :param openai_api_request_timeout: how many seconds to wait for openai api\n :param ckpt_dir: checkpoint dir\n :param skill_library_dir: skill library dir\n :param resume: whether to resume from checkpoint\n \"\"\"\n # init env\n self.env = VoyagerEnv(\n mc_port=mc_port,\n azure_login=azure_login,\n server_port=server_port,\n request_timeout=env_request_timeout,\n )\n self.env_wait_ticks = env_wait_ticks\n self.reset_placed_if_failed = reset_placed_if_failed\n self.max_iterations = max_iterations\n # set openai api key\n os.environ[\"OPENAI_API_KEY\"] = openai_api_key\n # init agents\n self.action_agent = ActionAgent(\n model_name=action_agent_model_name,\n temperature=action_agent_temperature,", + "type": "code", + "location": "/voyager/voyager.py:95-120" + }, + "455": { + "file_id": 51, + "content": "This code initializes a VoyagerEnv environment, sets environment variables, and initializes an ActionAgent. The environment is initialized with given parameters like mc_port, azure_login, server_port, request_timeout, env_request_timeout, reset_placed_if_failed, and max_iterations. OpenAI API key is set using os.environ. An ActionAgent is initialized with model_name and temperature.", + "type": "comment" + }, + "456": { + "file_id": 51, + "content": " request_timout=openai_api_request_timeout,\n ckpt_dir=ckpt_dir,\n resume=resume,\n chat_log=action_agent_show_chat_log,\n execution_error=action_agent_show_execution_error,\n )\n self.action_agent_task_max_retries = action_agent_task_max_retries\n self.curriculum_agent = CurriculumAgent(\n model_name=curriculum_agent_model_name,\n temperature=curriculum_agent_temperature,\n qa_model_name=curriculum_agent_qa_model_name,\n qa_temperature=curriculum_agent_qa_temperature,\n request_timout=openai_api_request_timeout,\n ckpt_dir=ckpt_dir,\n resume=resume,\n mode=curriculum_agent_mode,\n warm_up=curriculum_agent_warm_up,\n core_inventory_items=curriculum_agent_core_inventory_items,\n )\n self.critic_agent = CriticAgent(\n model_name=critic_agent_model_name,\n temperature=critic_agent_temperature,\n request_timout=openai_api_request_timeout,", + "type": "code", + "location": "/voyager/voyager.py:121-143" + }, + "457": { + "file_id": 51, + "content": "The code initializes three agent instances: `action_agent`, `curriculum_agent`, and `critic_agent`. Each agent is initialized with specific parameters, such as model names, temperatures, request timeouts, checkpoint directories, modes, and warm-up values. These agents are likely used for different tasks within the AI system.", + "type": "comment" + }, + "458": { + "file_id": 51, + "content": " mode=critic_agent_mode,\n )\n self.skill_manager = SkillManager(\n model_name=skill_manager_model_name,\n temperature=skill_manager_temperature,\n retrieval_top_k=skill_manager_retrieval_top_k,\n request_timout=openai_api_request_timeout,\n ckpt_dir=skill_library_dir if skill_library_dir else ckpt_dir,\n resume=True if resume or skill_library_dir else False,\n )\n self.recorder = U.EventRecorder(ckpt_dir=ckpt_dir, resume=resume)\n self.resume = resume\n # init variables for rollout\n self.action_agent_rollout_num_iter = -1\n self.task = None\n self.context = \"\"\n self.messages = None\n self.conversations = []\n self.last_events = None\n def reset(self, task, context=\"\", reset_env=True):\n self.action_agent_rollout_num_iter = 0\n self.task = task\n self.context = context\n if reset_env:\n self.env.reset(\n options={\n \"mode\": \"soft\",", + "type": "code", + "location": "/voyager/voyager.py:144-172" + }, + "459": { + "file_id": 51, + "content": "This code initializes a Voyager agent with the specified critic agent mode and skill manager parameters. It also creates an event recorder and sets up variables for rollout. The reset function resets the agent's state with the given task and context, optionally resetting the environment as well.", + "type": "comment" + }, + "460": { + "file_id": 51, + "content": " \"wait_ticks\": self.env_wait_ticks,\n }\n )\n difficulty = (\n \"easy\" if len(self.curriculum_agent.completed_tasks) > 15 else \"peaceful\"\n )\n # step to peek an observation\n events = self.env.step(\n \"bot.chat(`/time set ${getNextTime()}`);\\n\"\n + f\"bot.chat('/difficulty {difficulty}');\"\n )\n skills = self.skill_manager.retrieve_skills(query=self.context)\n print(\n f\"\\033[33mRender Action Agent system message with {len(skills)} skills\\033[0m\"\n )\n system_message = self.action_agent.render_system_message(skills=skills)\n human_message = self.action_agent.render_human_message(\n events=events, code=\"\", task=self.task, context=context, critique=\"\"\n )\n self.messages = [system_message, human_message]\n print(\n f\"\\033[32m****Action Agent human message****\\n{human_message.content}\\033[0m\"\n )\n assert len(self.messages) == 2", + "type": "code", + "location": "/voyager/voyager.py:173-196" + }, + "461": { + "file_id": 51, + "content": "This code is setting up the environment for an agent, defining difficulty level and retrieving skills. It then renders a system message and a human message using the ActionAgent. The messages are stored in self.messages and their length is checked to ensure there are two messages. A debug statement is also printed to display the human message content.", + "type": "comment" + }, + "462": { + "file_id": 51, + "content": " self.conversations = []\n return self.messages\n def close(self):\n self.env.close()\n def step(self):\n if self.action_agent_rollout_num_iter < 0:\n raise ValueError(\"Agent must be reset before stepping\")\n ai_message = self.action_agent.llm(self.messages)\n print(f\"\\033[34m****Action Agent ai message****\\n{ai_message.content}\\033[0m\")\n self.conversations.append(\n (self.messages[0].content, self.messages[1].content, ai_message.content)\n )\n parsed_result = self.action_agent.process_ai_message(message=ai_message)\n success = False\n if isinstance(parsed_result, dict):\n code = parsed_result[\"program_code\"] + \"\\n\" + parsed_result[\"exec_code\"]\n events = self.env.step(\n code,\n programs=self.skill_manager.programs,\n )\n self.recorder.record(events, self.task)\n self.action_agent.update_chest_memory(events[-1][1][\"nearbyChests\"])\n success, critique = self.critic_agent.check_task_success(", + "type": "code", + "location": "/voyager/voyager.py:197-221" + }, + "463": { + "file_id": 51, + "content": "This code initializes an empty conversations list, returns the messages, closes the environment, steps through the agent's actions, and updates the critic agent's success check.", + "type": "comment" + }, + "464": { + "file_id": 51, + "content": " events=events,\n task=self.task,\n context=self.context,\n chest_observation=self.action_agent.render_chest_observation(),\n max_retries=5,\n )\n if self.reset_placed_if_failed and not success:\n # revert all the placing event in the last step\n blocks = []\n positions = []\n for event_type, event in events:\n if event_type == \"onSave\" and event[\"onSave\"].endswith(\"_placed\"):\n block = event[\"onSave\"].split(\"_placed\")[0]\n position = event[\"status\"][\"position\"]\n blocks.append(block)\n positions.append(position)\n new_events = self.env.step(\n f\"await givePlacedItemBack(bot, {U.json_dumps(blocks)}, {U.json_dumps(positions)})\",\n programs=self.skill_manager.programs,\n )\n events[-1][1][\"inventory\"] = new_events[-1][1][\"inventory\"]", + "type": "code", + "location": "/voyager/voyager.py:222-243" + }, + "465": { + "file_id": 51, + "content": "This code is creating an event object and calling a step function to execute it. If the execution fails, it reverts all placing events in the last step by using givePlacedItemBack function and updating inventory.", + "type": "comment" + }, + "466": { + "file_id": 51, + "content": " events[-1][1][\"voxels\"] = new_events[-1][1][\"voxels\"]\n new_skills = self.skill_manager.retrieve_skills(\n query=self.context\n + \"\\n\\n\"\n + self.action_agent.summarize_chatlog(events)\n )\n system_message = self.action_agent.render_system_message(skills=new_skills)\n human_message = self.action_agent.render_human_message(\n events=events,\n code=parsed_result[\"program_code\"],\n task=self.task,\n context=self.context,\n critique=critique,\n )\n self.last_events = copy.deepcopy(events)\n self.messages = [system_message, human_message]\n else:\n assert isinstance(parsed_result, str)\n self.recorder.record([], self.task)\n print(f\"\\033[34m{parsed_result} Trying again!\\033[0m\")\n assert len(self.messages) == 2\n self.action_agent_rollout_num_iter += 1\n done = (\n self.action_agent_rollout_num_iter >= self.action_agent_task_max_retries", + "type": "code", + "location": "/voyager/voyager.py:244-267" + }, + "467": { + "file_id": 51, + "content": "The code updates the events list, retrieves new skills using the SkillManager, generates system and human messages, handles failed parsing attempts, records results, prints a message and increments rollout number if max retries are reached.", + "type": "comment" + }, + "468": { + "file_id": 51, + "content": " or success\n )\n info = {\n \"task\": self.task,\n \"success\": success,\n \"conversations\": self.conversations,\n }\n if success:\n assert (\n \"program_code\" in parsed_result and \"program_name\" in parsed_result\n ), \"program and program_name must be returned when success\"\n info[\"program_code\"] = parsed_result[\"program_code\"]\n info[\"program_name\"] = parsed_result[\"program_name\"]\n else:\n print(\n f\"\\033[32m****Action Agent human message****\\n{self.messages[-1].content}\\033[0m\"\n )\n return self.messages, 0, done, info\n def rollout(self, *, task, context, reset_env=True):\n self.reset(task=task, context=context, reset_env=reset_env)\n while True:\n messages, reward, done, info = self.step()\n if done:\n break\n return messages, reward, done, info\n def learn(self, reset_env=True):\n if self.resume:", + "type": "code", + "location": "/voyager/voyager.py:268-296" + }, + "469": { + "file_id": 51, + "content": "The code snippet defines a class with methods for rollout and learn. The rollout method performs continuous steps until done, returning messages, reward, and information. If successful, it retrieves program_code and program_name from parsed result. Otherwise, it prints the last human message. The learn method resumes learning if self.resume is true.", + "type": "comment" + }, + "470": { + "file_id": 51, + "content": " # keep the inventory\n self.env.reset(\n options={\n \"mode\": \"soft\",\n \"wait_ticks\": self.env_wait_ticks,\n }\n )\n else:\n # clear the inventory\n self.env.reset(\n options={\n \"mode\": \"hard\",\n \"wait_ticks\": self.env_wait_ticks,\n }\n )\n self.resume = True\n self.last_events = self.env.step(\"\")\n while True:\n if self.recorder.iteration > self.max_iterations:\n print(\"Iteration limit reached\")\n break\n task, context = self.curriculum_agent.propose_next_task(\n events=self.last_events,\n chest_observation=self.action_agent.render_chest_observation(),\n max_retries=5,\n )\n print(\n f\"\\033[35mStarting task {task} for at most {self.action_agent_task_max_retries} times\\033[0m\"\n )", + "type": "code", + "location": "/voyager/voyager.py:297-326" + }, + "471": { + "file_id": 51, + "content": "The code resets the environment and handles inventory management depending on a condition. It then checks if the iteration limit is reached, proposes the next task using curriculum agent, and prints the start of the task with maximum retries specified.", + "type": "comment" + }, + "472": { + "file_id": 51, + "content": " try:\n messages, reward, done, info = self.rollout(\n task=task,\n context=context,\n reset_env=reset_env,\n )\n except Exception as e:\n time.sleep(3) # wait for mineflayer to exit\n info = {\n \"task\": task,\n \"success\": False,\n }\n # reset bot status here\n self.last_events = self.env.reset(\n options={\n \"mode\": \"hard\",\n \"wait_ticks\": self.env_wait_ticks,\n \"inventory\": self.last_events[-1][1][\"inventory\"],\n \"equipment\": self.last_events[-1][1][\"status\"][\"equipment\"],\n \"position\": self.last_events[-1][1][\"status\"][\"position\"],\n }\n )\n # use red color background to print the error\n print(\"Your last round rollout terminated due to error:\")", + "type": "code", + "location": "/voyager/voyager.py:327-350" + }, + "473": { + "file_id": 51, + "content": "Code attempts to perform a rollout task, but if an exception occurs, it sleeps for 3 seconds, resets the bot status with specific options, prints an error message, and returns information with \"success\" set to False.", + "type": "comment" + }, + "474": { + "file_id": 51, + "content": " print(f\"\\033[41m{e}\\033[0m\")\n if info[\"success\"]:\n self.skill_manager.add_new_skill(info)\n self.curriculum_agent.update_exploration_progress(info)\n print(\n f\"\\033[35mCompleted tasks: {', '.join(self.curriculum_agent.completed_tasks)}\\033[0m\"\n )\n print(\n f\"\\033[35mFailed tasks: {', '.join(self.curriculum_agent.failed_tasks)}\\033[0m\"\n )\n return {\n \"completed_tasks\": self.curriculum_agent.completed_tasks,\n \"failed_tasks\": self.curriculum_agent.failed_tasks,\n \"skills\": self.skill_manager.skills,\n }\n def decompose_task(self, task):\n if not self.last_events:\n self.last_events = self.env.reset(\n options={\n \"mode\": \"hard\",\n \"wait_ticks\": self.env_wait_ticks,\n }\n )\n return self.curriculum_agent.decompose_task(task, self.last_events)\n def inference(self, task=None, sub_goals=[], reset_mode=\"hard\", reset_env=True):", + "type": "code", + "location": "/voyager/voyager.py:351-380" + }, + "475": { + "file_id": 51, + "content": "The code includes functions for handling tasks, adding new skills, updating exploration progress, and retrieving completed and failed tasks. It also handles the environment reset and inference.", + "type": "comment" + }, + "476": { + "file_id": 51, + "content": " if not task and not sub_goals:\n raise ValueError(\"Either task or sub_goals must be provided\")\n if not sub_goals:\n sub_goals = self.decompose_task(task)\n self.env.reset(\n options={\n \"mode\": reset_mode,\n \"wait_ticks\": self.env_wait_ticks,\n }\n )\n self.curriculum_agent.completed_tasks = []\n self.curriculum_agent.failed_tasks = []\n self.last_events = self.env.step(\"\")\n while self.curriculum_agent.progress < len(sub_goals):\n next_task = sub_goals[self.curriculum_agent.progress]\n context = self.curriculum_agent.get_task_context(next_task)\n print(\n f\"\\033[35mStarting task {next_task} for at most {self.action_agent_task_max_retries} times\\033[0m\"\n )\n messages, reward, done, info = self.rollout(\n task=next_task,\n context=context,\n reset_env=reset_env,\n )\n self.curriculum_agent.update_exploration_progress(info)", + "type": "code", + "location": "/voyager/voyager.py:381-405" + }, + "477": { + "file_id": 51, + "content": "Checks if either task or sub_goals are provided, and resets the environment. Then iterates through sub_goals, prints current goal and runs rollout for each one, updating exploration progress along the way.", + "type": "comment" + }, + "478": { + "file_id": 51, + "content": " print(\n f\"\\033[35mCompleted tasks: {', '.join(self.curriculum_agent.completed_tasks)}\\033[0m\"\n )\n print(\n f\"\\033[35mFailed tasks: {', '.join(self.curriculum_agent.failed_tasks)}\\033[0m\"\n )", + "type": "code", + "location": "/voyager/voyager.py:406-411" + }, + "479": { + "file_id": 51, + "content": "This code is printing the completed and failed tasks in a formatted way using ANSI escape sequences for coloring.", + "type": "comment" + } +} \ No newline at end of file diff --git a/docs/data/titles/0.json b/docs/data/titles/0.json new file mode 100644 index 00000000..1e1eae75 --- /dev/null +++ b/docs/data/titles/0.json @@ -0,0 +1,226 @@ +{ + "/FAQ.md": "Voyager FAQs: Solutions and Costs", + "/FAQ.md:1-12": "Voyager FAQ: Troubleshooting and Usage", + "/FAQ.md:12-18": "Fix 'KeyError: 'access_token'' by Changing Redirect URI Type", + "/FAQ.md:18-38": "Troubleshooting Mineflayer Installation", + "/FAQ.md:40-45": "Resetting Environment & Bot View in Mineflayer", + "/FAQ.md:48-54": "Upgrading from GPT-3.5 to GPT-4", + "/README.md": "AI Mastermind Tackles Minecraft, Excels", + "/README.md:1-24": "Embodied Agent for Minecraft Exploration", + "/README.md:101-130": "Setting Up Voyager Minecraft World", + "/README.md:131-157": "Voyager Agent Decomposes Tasks for Inference", + "/README.md:158-164": "Research Paper Metadata", + "/README.md:25-38": "Voyager: SOTA Skill Library with GPT-4 Integration and Lifelong Learning", + "/README.md:40-72": "Install Voyager: Python 3.9, Node.js 16.13.0, Minecraft", + "/README.md:74-100": "Setup and Play Voyager", + "/installation/fabric_mods_install.md": "Fabric Mods Installation Guide", + "/installation/fabric_mods_install.md:1-6": "Fabric Mods: Installation Guide", + "/installation/fabric_mods_install.md:13-22": "Fabric Mod Installation: Azure Login Setup", + "/installation/fabric_mods_install.md:7-13": "Fabric Mods Installation Guide", + "/installation/minecraft_instance_install.md": "Setup Azure Login for Minecraft Voyager", + "/installation/minecraft_instance_install.md:1-12": "Voyager Minecraft on Azure Setup", + "/installation/minecraft_instance_install.md:13-22": "Collect Azure Login Info", + "/installation/minecraft_instance_install.md:23-41": "Azure Login and Minecraft Instance Setup", + "/requirements.txt": "Essential Libraries for Project Functionality", + "/setup.py": "Setting up a Python Package", + "/setup.py:1-42": "Setting Up Voyager Python Package", + "/setup.py:43-59": "Python Package Setup with Setup.py", + "/skill_library/README.md": "Skill Library: Community Contributions and Sharing", + "/skill_library/README.md:1-34": "Skill Library Contributions", + "/skill_library/README.md:35-44": "Shared Skill Library", + "/voyager/__init__.py": "Importing Voyager Class", + "/voyager/agents/__init__.py": "Importing Voyager's Agents", + "/voyager/agents/action.py": "ActionAgent: Streamlined ChatOpenAI-Powered Gaming Agent", + "/voyager/agents/action.py:1-34": "ActionAgent Class Initialization and Features", + "/voyager/agents/action.py:113-135": "Event Handler and Observation String Construction", + "/voyager/agents/action.py:137-166": "Agent Observation Generation Code Snippet", + "/voyager/agents/action.py:168-199": "Agent Observation String Generation", + "/voyager/agents/action.py:201-226": "Parsing AI Messages with JavaScript Support", + "/voyager/agents/action.py:227-246": "Last Async Function Executor", + "/voyager/agents/action.py:247-270": "Action Response Parser and Chat Log Filter", + "/voyager/agents/action.py:271-280": "Event Filter and Chatlog Updater", + "/voyager/agents/action.py:35-59": "Chest Memory Agent Class", + "/voyager/agents/action.py:60-86": "Chest Information Fetcher", + "/voyager/agents/action.py:87-112": "System Message Initialization and Rendering", + "/voyager/agents/critic.py": "Voyager CriticAgent: Manual or Auto Mode, Task Evaluation", + "/voyager/agents/critic.py:1-32": "Critic Agent OpenAI Chat Class", + "/voyager/agents/critic.py:120-138": "Critic Agent: Initialize and Check Task Success", + "/voyager/agents/critic.py:33-60": "Observation Construction", + "/voyager/agents/critic.py:62-91": "AI-Assisted Task Evaluation and User Critique", + "/voyager/agents/critic.py:92-119": "Critic Agent JSON Parsing and Retry", + "/voyager/agents/curriculum.py": "Curriculum Agent: Task-Based Learning with LangChain", + "/voyager/agents/curriculum.py:1-37": "Curriculum Agent Initializes LangChain LLMs", + "/voyager/agents/curriculum.py:119-149": "Voyager Agent Class Definition", + "/voyager/agents/curriculum.py:150-183": "Biome Detector Algorithm", + "/voyager/agents/curriculum.py:184-202": "Agent Progress and Inventory Observation", + "/voyager/agents/curriculum.py:203-228": "Contextual Question Generator", + "/voyager/agents/curriculum.py:229-251": "Curriculum Agent: Task Proposal and Mining", + "/voyager/agents/curriculum.py:252-264": "Chest Inventory Management", + "/voyager/agents/curriculum.py:265-290": "Craft or Place Chest Task", + "/voyager/agents/curriculum.py:292-315": "AI Task Retrieval and Parsing", + "/voyager/agents/curriculum.py:316-343": "Curriculum Agent Functions", + "/voyager/agents/curriculum.py:344-372": "Curriculum Task Cleaner and Decomposer", + "/voyager/agents/curriculum.py:373-397": "Curriculum Agent Task Decomposer", + "/voyager/agents/curriculum.py:38-63": "Initializing Curriculum Agent", + "/voyager/agents/curriculum.py:398-418": "Cache and Generate QA Pairs in Curriculum Agent", + "/voyager/agents/curriculum.py:419-441": "AI Game Agent: Question-Answer Cache", + "/voyager/agents/curriculum.py:442-465": "Methods for Generating Messages and Asking Questions in Minecraft", + "/voyager/agents/curriculum.py:466-490": "Question Pair Extraction and Verification", + "/voyager/agents/curriculum.py:491-498": "QA Agent Message Rendering", + "/voyager/agents/curriculum.py:64-82": "Initializing Curriculum Agent's QA Cache", + "/voyager/agents/curriculum.py:83-118": "Default Warm-Up Values for Curriculum Agent", + "/voyager/agents/skill.py": "Skill Manager Initialization", + "/voyager/agents/skill.py:1-34": "Skill Manager Initialization", + "/voyager/agents/skill.py:109-127": "Skill Search with Vector Database", + "/voyager/agents/skill.py:35-57": "Skill Manager Initialization", + "/voyager/agents/skill.py:58-80": "Generate and Add Skills in Database", + "/voyager/agents/skill.py:81-108": "Skill Management Code", + "/voyager/control_primitives/__init__.py": "Load Voyager Control Primitives", + "/voyager/control_primitives/craftHelper.js": "Crafting Helper", + "/voyager/control_primitives/craftHelper.js:1-26": "Crafting Table Recipe Selector", + "/voyager/control_primitives/craftHelper.js:27-54": "Check Recipe for Missing Items", + "/voyager/control_primitives/craftHelper.js:55-61": "Craft Helper Checks Items", + "/voyager/control_primitives/craftItem.js": "Asynchronous Crafting Validation with Feedback", + "/voyager/control_primitives/craftItem.js:1-30": "Asynchronous Crafting Function", + "/voyager/control_primitives/craftItem.js:31-43": "Error-catching Recipe Craft Limit", + "/voyager/control_primitives/exploreUntil.js": "Explore Timeout Function", + "/voyager/control_primitives/exploreUntil.js:1-33": "Explore Until Function Guide", + "/voyager/control_primitives/exploreUntil.js:34-67": "Interval-based Exploration Function", + "/voyager/control_primitives/exploreUntil.js:68-87": "Explore Until Timeout", + "/voyager/control_primitives/givePlacedItemBack.js": "Disable, Enable Tile Drops and Give Back Items", + "/voyager/control_primitives/givePlacedItemBack.js:1-24": "Re-enable Tile Drops for Bots", + "/voyager/control_primitives/givePlacedItemBack.js:25-38": "Air and Destroy Block", + "/voyager/control_primitives/killMob.js": "Kill Mob Progress Saving", + "/voyager/control_primitives/killMob.js:1-32": "Check Mob Presence and Distance", + "/voyager/control_primitives/killMob.js:33-51": "Kill and Collect: Mob Script", + "/voyager/control_primitives/mineBlock.js": "MineBlock: Collecting and Verifying Bot Operations", + "/voyager/control_primitives/mineBlock.js:1-32": "Mine Block Function", + "/voyager/control_primitives/mineBlock.js:33-37": "Mine Block State Save", + "/voyager/control_primitives/placeItem.js": "Verifying and Placing Inventory Items", + "/voyager/control_primitives/placeItem.js:1-32": "Place Item Function: Checking and Placement", + "/voyager/control_primitives/placeItem.js:33-62": "Place Item Control Primitives", + "/voyager/control_primitives/placeItem.js:63-79": "Verify and Save Inventory Items", + "/voyager/control_primitives/shoot.js": "Auto-Attack Bot Function", + "/voyager/control_primitives/smeltItem.js": "Smelt Item Function for Bots", + "/voyager/control_primitives/smeltItem.js:1-29": "Smelt Item with Furnace", + "/voyager/control_primitives/smeltItem.js:30-56": "Smelting Bot Items in Furnace", + "/voyager/control_primitives/smeltItem.js:57-68": "Smelt Failure Handling", + "/voyager/control_primitives/useChest.js": "Bot-Controlled Chest Interaction", + "/voyager/control_primitives/useChest.js:1-31": "Chest Item Retrieval Function", + "/voyager/control_primitives/useChest.js:32-62": "Chest Deposit Function", + "/voyager/control_primitives/useChest.js:63-94": "Chest Positioning and Deposit Code Snippet", + "/voyager/control_primitives/useChest.js:95-133": "Interacting with Chests in Games", + "/voyager/control_primitives/waitForMobRemoved.js": "Wait for Mob Removal", + "/voyager/control_primitives/waitForMobRemoved.js:1-31": "Wait for Mob Removal Detection", + "/voyager/control_primitives/waitForMobRemoved.js:32-65": "Wait for Mob Removal & Item Drop", + "/voyager/control_primitives/waitForMobRemoved.js:66-84": "Detect Mob Removal with Event Listeners", + "/voyager/control_primitives_context/__init__.py": "Voyager Control Primitives Context", + "/voyager/control_primitives_context/craftItem.js": "Crafting Recipe: Follow and Create", + "/voyager/control_primitives_context/exploreUntil.js": "Exploring Iron Ore and Pigs with Voyager's exploreUntil", + "/voyager/control_primitives_context/exploreUntil.js:1-29": "Explore Until: Iron Ore and Pig Detector", + "/voyager/control_primitives_context/exploreUntil.js:30-31": "Explore Until: Voyager's Control Primitives", + "/voyager/control_primitives_context/killMob.js": "Mob Killing Function: killMob", + "/voyager/control_primitives_context/mineBlock.js": "Mine Block Efficiently", + "/voyager/control_primitives_context/mineflayer.js": "Mastering Mineflayer Bot Controls", + "/voyager/control_primitives_context/mineflayer.js:1-10": "Mineflayer Goals and Functions", + "/voyager/control_primitives_context/mineflayer.js:11-20": "Mineflayer: Minecraft Bot Functions", + "/voyager/control_primitives_context/mineflayer.js:20-22": "Activating and Using Items in Mineflayer.js", + "/voyager/control_primitives_context/placeItem.js": "Item Placement Algorithm", + "/voyager/control_primitives_context/placeItem.js:1-25": "Place Item Near Player", + "/voyager/control_primitives_context/placeItem.js:26-28": "Equip and Place Block in Bot's Hand", + "/voyager/control_primitives_context/smeltItem.js": "Furnace Smelting Function", + "/voyager/control_primitives_context/useChest.js": "Chest Deposit Functions", + "/voyager/control_primitives_context/useChest.js:1-19": "Interact with Chest at (30, 65, 100)", + "/voyager/control_primitives_context/useChest.js:20-35": "Chest Interaction Functions", + "/voyager/env/__init__.py": "Voyager Env Package", + "/voyager/env/bridge.py": "Azure Minecraft Server Control with VoyagerEnv", + "/voyager/env/bridge.py:1-38": "VoyagerEnv: Minecraft Environment Class", + "/voyager/env/bridge.py:127-158": "Reset and Render Methods", + "/voyager/env/bridge.py:159-189": "Stop, Pause, Unpause Minecraft Server API", + "/voyager/env/bridge.py:39-68": "Minecraft Server Setup with Mineflayer", + "/voyager/env/bridge.py:69-93": "Minecraft Server Monitor & Restarter", + "/voyager/env/bridge.py:94-125": "Minecraft Server Control Class", + "/voyager/env/minecraft_launcher.py": "Minecraft Launcher Script", + "/voyager/env/minecraft_launcher.py:1-41": "Minecraft Launcher Class Definition", + "/voyager/env/minecraft_launcher.py:101-127": "Minecraft Server Launcher Class", + "/voyager/env/minecraft_launcher.py:42-69": "Initialize Mineflayer Process", + "/voyager/env/minecraft_launcher.py:70-100": "Minecraft Launcher Login Process", + "/voyager/env/process_monitor.py": "Subprocess Lifecycle Manager", + "/voyager/env/process_monitor.py:1-38": "Process Monitoring Class", + "/voyager/env/process_monitor.py:39-65": "Subprocess Monitor and Callback Setup", + "/voyager/env/process_monitor.py:66-90": "Process Manager Class with Lifecycle Control", + "/voyager/prompts/__init__.py": "Loading Prompts via Voyager", + "/voyager/prompts/action_response_format.txt": "Asynchronous Main Function: \"yourMainFunctionName\"", + "/voyager/prompts/action_template.txt": "Reusable Mineflayer", + "/voyager/prompts/action_template.txt:1-32": "Automate Minecraft with Mineflayer", + "/voyager/prompts/action_template.txt:33-39": "Efficient Botless Building", + "/voyager/prompts/action_template.txt:40-49": "Chat Bot Code Guidelines", + "/voyager/prompts/critic.txt": "Minecraft Task Evaluation Critic", + "/voyager/prompts/critic.txt:1-16": "Minecraft Task Progress Assessment", + "/voyager/prompts/critic.txt:113-127": "Inventory Optimizer", + "/voyager/prompts/critic.txt:17-59": "JSON Response Generator and Critiquer", + "/voyager/prompts/critic.txt:60-113": "Zombie Survival Game Task Evaluator", + "/voyager/prompts/curriculum.txt": "Minecraft Task Suggestions", + "/voyager/prompts/curriculum.txt:1-26": "Minecraft Assistant: Personalized Learning Path", + "/voyager/prompts/curriculum.txt:27-32": "Task Formatting Guidelines", + "/voyager/prompts/curriculum.txt:33-42": "Task Avoidance Guidelines and Examples", + "/voyager/prompts/curriculum_qa_step1_ask_questions.txt": "Minecraft Biome Prompts Generator", + "/voyager/prompts/curriculum_qa_step1_ask_questions.txt:1-24": "Ask Minecraft Questions AI", + "/voyager/prompts/curriculum_qa_step1_ask_questions.txt:25-46": "Effective Prompts for Curriculum", + "/voyager/prompts/curriculum_qa_step1_ask_questions.txt:47-68": "Game Prompt Questions", + "/voyager/prompts/curriculum_qa_step1_ask_questions.txt:68-94": "Ask Specific Questions", + "/voyager/prompts/curriculum_qa_step2_answer_questions.txt": "Minecraft Q&A Helper", + "/voyager/prompts/curriculum_task_decomposition.txt": "Minecraft Task Decomposition AI", + "/voyager/prompts/skill.txt": "Mine Cobblestone with Wooden Pickaxe", + "/voyager/prompts/skill.txt:1-24": "Craft and Collect Cobblestones", + "/voyager/prompts/skill.txt:25-51": "Wooden Pickaxe Mine Event", + "/voyager/utils/__init__.py": "Voyager Utilities", + "/voyager/utils/file_utils.py": "File Utilities: Comprehensive Python File Operations", + "/voyager/utils/file_utils.py:1-65": "File Utility Functions", + "/voyager/utils/file_utils.py:108-136": "Directory Scanner: Recursive, Filtered File Listing", + "/voyager/utils/file_utils.py:137-177": "File Utilities: List, Create Directories", + "/voyager/utils/file_utils.py:178-227": "File Utility Functions", + "/voyager/utils/file_utils.py:228-262": "File and Directory Utilities", + "/voyager/utils/file_utils.py:263-299": "Copy Tree Function for Files and Folders", + "/voyager/utils/file_utils.py:300-319": "Symlink-Aware File Copying: Preserving Permissions and Handling Dangling Symlinks", + "/voyager/utils/file_utils.py:320-350": "Ignore Patterns for CopyTree", + "/voyager/utils/file_utils.py:351-387": "File Path Manipulation Utilities", + "/voyager/utils/file_utils.py:388-431": "File Utilities: Path and Directory Manipulation", + "/voyager/utils/file_utils.py:434-463": "MD5 Checksum & Tarball Creation Utility", + "/voyager/utils/file_utils.py:465-496": "Voyager File Utilities: Archive, Extract, and Move with Backup", + "/voyager/utils/file_utils.py:499-538": "File Utilities and Locking", + "/voyager/utils/file_utils.py:539-568": "Load and Dump Data Functions", + "/voyager/utils/file_utils.py:66-107": "File Utilities: Expand, Join, Exist", + "/voyager/utils/json_utils.py": "JSON Validator with Aliases and Corrections", + "/voyager/utils/json_utils.py:1-44": "JSON Functions and Error Handling", + "/voyager/utils/json_utils.py:121-152": "Fixing and Parsing Invalid JSON Strings", + "/voyager/utils/json_utils.py:153-176": "Handling JSON Strings with Preceding Text", + "/voyager/utils/json_utils.py:177-200": "AI-Driven JSON Repair and Loading", + "/voyager/utils/json_utils.py:201-226": "Fix Invalid JSON Format with Triple Backticks", + "/voyager/utils/json_utils.py:227-227": "Conditional 'Failed' Return", + "/voyager/utils/json_utils.py:45-83": "JSON Syntax Utilities", + "/voyager/utils/json_utils.py:84-120": "Fix Invalid JSON Errors", + "/voyager/utils/record_utils.py": "EventRecorder: Record, Update, Sort", + "/voyager/utils/record_utils.py:1-38": "Event Recorder Initialization and Management", + "/voyager/utils/record_utils.py:39-64": "Recorder: Timestamped Event Recording Class", + "/voyager/utils/record_utils.py:65-88": "Event Data Tracking and History", + "/voyager/utils/record_utils.py:89-102": "Recording Position and Elapsed Time Updates", + "/voyager/voyager.py": "Voyager: Comprehensive AI Communication Tool", + "/voyager/voyager.py:1-35": "Voyager: AI Agent's Main Module", + "/voyager/voyager.py:121-143": "Agent Instantiation in Voyager AI System", + "/voyager/voyager.py:144-172": "Voyager Agent Initialization and Resetting", + "/voyager/voyager.py:173-196": "Setting Up Agent Environment and Messages", + "/voyager/voyager.py:197-221": "Agent Actions and Conversations Initiation", + "/voyager/voyager.py:222-243": "Reverting Failed Events in Voyager.py", + "/voyager/voyager.py:244-267": "Skill Updates and Retry Handling", + "/voyager/voyager.py:268-296": "Rollout and Learn in Voyager Class", + "/voyager/voyager.py:297-326": "Code: Environment Reset and Inventory Management", + "/voyager/voyager.py:327-350": "Error Handling Rollout Task", + "/voyager/voyager.py:351-380": "Voyager Python Functions", + "/voyager/voyager.py:36-57": "Voyager Class: Iterative Prompting, Curriculum Agent", + "/voyager/voyager.py:381-405": "Reset and Iterate Sub-Goals Rollout", + "/voyager/voyager.py:406-411": "Colorful Task Summary Printing", + "/voyager/voyager.py:58-72": "Minecraft Bot Configuration Parameters", + "/voyager/voyager.py:73-94": "Curriculum and Agent Parameters", + "/voyager/voyager.py:95-120": "VoyagerEnv and ActionAgent Initiation" +} \ No newline at end of file diff --git a/docs/doc/0019236a-6729-4da1-a04c-55f498b1759a.json b/docs/doc/0019236a-6729-4da1-a04c-55f498b1759a.json new file mode 100644 index 00000000..08de8f6c --- /dev/null +++ b/docs/doc/0019236a-6729-4da1-a04c-55f498b1759a.json @@ -0,0 +1,10 @@ +{ + "summary": "The code imports the VoyagerEnv class from the bridge module to be used in this package. It appears to be an environment setting or configuration for a project called \"Voyager\".", + "details": [ + { + "comment": "The code imports the VoyagerEnv class from the bridge module to be used in this package. It appears to be an environment setting or configuration for a project called \"Voyager\".", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/env/__init__.py\":0-0", + "content": "from .bridge import VoyagerEnv" + } + ] +} \ No newline at end of file diff --git a/docs/doc/048d61f5-a5dc-493a-9719-6bf09f0337bd.json b/docs/doc/048d61f5-a5dc-493a-9719-6bf09f0337bd.json new file mode 100644 index 00000000..52be1262 --- /dev/null +++ b/docs/doc/048d61f5-a5dc-493a-9719-6bf09f0337bd.json @@ -0,0 +1,10 @@ +{ + "summary": "This line imports the Voyager class from the voyager module within the same package, allowing access to its methods and attributes.", + "details": [ + { + "comment": "This line imports the Voyager class from the voyager module within the same package, allowing access to its methods and attributes.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/__init__.py\":0-0", + "content": "from .voyager import Voyager" + } + ] +} \ No newline at end of file diff --git a/docs/doc/088b2761-bd4c-4da7-a402-262cbaab4d82.json b/docs/doc/088b2761-bd4c-4da7-a402-262cbaab4d82.json new file mode 100644 index 00000000..87aa60ac --- /dev/null +++ b/docs/doc/088b2761-bd4c-4da7-a402-262cbaab4d82.json @@ -0,0 +1,105 @@ +{ + "summary": "The Curriculum Agent uses LangChain's ChatOpenAI and QA_ChatOpenAI for task-based learning, managing cache and tasks, storing info, calculating progress, generating observations during warm-up phase, and includes inventory usage checks. This AI game agent code handles QA interactions with the user, generates messages, asks questions, utilizes QA LLM for responses, processes question-concept pairs, and runs QA step 2 to answer questions.", + "details": [ + { + "comment": "Class \"CurriculumAgent\" initializes LangChain's ChatOpenAI and QA_ChatOpenAI for language generation, with various optional parameters like model name, temperature, timeout, resume, mode etc. These LLMs (Language Generation Models) will be used to handle user queries or requests in the curriculum-based agent.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/agents/curriculum.py\":0-36", + "content": "from __future__ import annotations\nimport random\nimport re\nimport voyager.utils as U\nfrom voyager.prompts import load_prompt\nfrom voyager.utils.json_utils import fix_and_parse_json\nfrom langchain.chat_models import ChatOpenAI\nfrom langchain.embeddings.openai import OpenAIEmbeddings\nfrom langchain.schema import HumanMessage, SystemMessage\nfrom langchain.vectorstores import Chroma\nclass CurriculumAgent:\n def __init__(\n self,\n model_name=\"gpt-3.5-turbo\",\n temperature=0,\n qa_model_name=\"gpt-3.5-turbo\",\n qa_temperature=0,\n request_timout=120,\n ckpt_dir=\"ckpt\",\n resume=False,\n mode=\"auto\",\n warm_up=None,\n core_inventory_items: str | None = None,\n ):\n self.llm = ChatOpenAI(\n model_name=model_name,\n temperature=temperature,\n request_timeout=request_timout,\n )\n self.qa_llm = ChatOpenAI(\n model_name=qa_model_name,\n temperature=qa_temperature,\n request_timeout=request_timout," + }, + { + "comment": "This code initializes a Curriculum Agent. It checks the mode (auto or manual), sets the checkpoint directory, and creates a curriculum/vectordb directory if it does not exist. If resuming from a checkpoint, it loads completed tasks, failed tasks, and QA cache from JSON files. Otherwise, it initializes empty lists for these tasks and an empty dictionary for the QA cache. The code then creates a Chroma vector database named \"qa_cache_questions_vectordb\" using the OpenAI embeddings function and specifies the persist directory as ckpt_dir/curriculum/vectordb. It asserts that the count of items in the collection is equal to the length of the loaded QA cache.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/agents/curriculum.py\":37-62", + "content": " )\n assert mode in [\n \"auto\",\n \"manual\",\n ], f\"mode {mode} not supported\"\n self.mode = mode\n self.ckpt_dir = ckpt_dir\n U.f_mkdir(f\"{ckpt_dir}/curriculum/vectordb\")\n if resume:\n print(f\"\\033[35mLoading Curriculum Agent from {ckpt_dir}/curriculum\\033[0m\")\n self.completed_tasks = U.load_json(\n f\"{ckpt_dir}/curriculum/completed_tasks.json\"\n )\n self.failed_tasks = U.load_json(f\"{ckpt_dir}/curriculum/failed_tasks.json\")\n self.qa_cache = U.load_json(f\"{ckpt_dir}/curriculum/qa_cache.json\")\n else:\n self.completed_tasks = []\n self.failed_tasks = []\n self.qa_cache = {}\n # vectordb for qa cache\n self.qa_cache_questions_vectordb = Chroma(\n collection_name=\"qa_cache_questions_vectordb\",\n embedding_function=OpenAIEmbeddings(),\n persist_directory=f\"{ckpt_dir}/curriculum/vectordb\",\n )\n assert self.qa_cache_questions_vectordb._collection.count() == len(" + }, + { + "comment": "This code is initializing the Curriculum Agent's QA cache, ensuring that the question vector database (vectordb) is synced with qa_cache.json. It checks if 'resume=False' was set during agent initialization and allows for the manual deletion of qa cache questions vectordb directory for running from scratch. The code also initializes the warm-up variable, ensuring it exists or setting missing values to default values.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/agents/curriculum.py\":63-81", + "content": " self.qa_cache\n ), (\n f\"Curriculum Agent's qa cache question vectordb is not synced with qa_cache.json.\\n\"\n f\"There are {self.qa_cache_questions_vectordb._collection.count()} questions in vectordb \"\n f\"but {len(self.qa_cache)} questions in qa_cache.json.\\n\"\n f\"Did you set resume=False when initializing the agent?\\n\"\n f\"You may need to manually delete the qa cache question vectordb directory for running from scratch.\\n\"\n )\n # if warm up not defined, initialize it as a dict, else, initialize all the missing value as a default value\n if not warm_up:\n warm_up = self.default_warmup\n self.warm_up = {}\n if \"optional_inventory_items\" in warm_up:\n assert core_inventory_items is not None\n self._core_inv_items_regex = re.compile(core_inventory_items)\n self.warm_up[\"optional_inventory_items\"] = warm_up[\n \"optional_inventory_items\"\n ]\n else:" + }, + { + "comment": "Sets default warm-up values for curriculum agent, overrides with provided warm-up dictionary.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/agents/curriculum.py\":82-117", + "content": " self.warm_up[\"optional_inventory_items\"] = 0\n for key in self.curriculum_observations:\n self.warm_up[key] = warm_up.get(key, self.default_warmup[key])\n self.warm_up[\"nearby_blocks\"] = 0\n self.warm_up[\"inventory\"] = 0\n self.warm_up[\"completed_tasks\"] = 0\n self.warm_up[\"failed_tasks\"] = 0\n @property\n def default_warmup(self):\n return {\n \"context\": 15,\n \"biome\": 10,\n \"time\": 15,\n \"nearby_blocks\": 0,\n \"other_blocks\": 10,\n \"nearby_entities\": 5,\n \"health\": 15,\n \"hunger\": 15,\n \"position\": 0,\n \"equipment\": 0,\n \"inventory\": 0,\n \"optional_inventory_items\": 7,\n \"chests\": 0,\n \"completed_tasks\": 0,\n \"failed_tasks\": 0,\n }\n @property\n def curriculum_observations(self):\n return [\n \"context\",\n \"biome\",\n \"time\",\n \"nearby_blocks\",\n \"other_blocks\"," + }, + { + "comment": "This code defines a class with properties for storing information like nearby entities, health, hunger, position, equipment, inventory, chest observations, completed tasks, and failed tasks. It also has methods to calculate progress, render system messages, and generate observations based on events and chest observations, including biome, time of day, voxels, block records, entities' status, and player's health and hunger levels.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/agents/curriculum.py\":118-148", + "content": " \"nearby_entities\",\n \"health\",\n \"hunger\",\n \"position\",\n \"equipment\",\n \"inventory\",\n \"chests\",\n \"completed_tasks\",\n \"failed_tasks\",\n ]\n @property\n def progress(self):\n return len(self.completed_tasks)\n def render_system_message(self):\n system_message = SystemMessage(content=load_prompt(\"curriculum\"))\n assert isinstance(system_message, SystemMessage)\n return system_message\n def render_observation(self, *, events, chest_observation):\n assert events[-1][0] == \"observe\", \"Last event must be observe\"\n event = events[-1][1]\n biome = event[\"status\"][\"biome\"]\n time_of_day = event[\"status\"][\"timeOfDay\"]\n voxels = event[\"voxels\"]\n block_records = event[\"blockRecords\"]\n entities = event[\"status\"][\"entities\"]\n health = event[\"status\"][\"health\"]\n hunger = event[\"status\"][\"food\"]\n position = event[\"status\"][\"position\"]" + }, + { + "comment": "This code block checks the equipment, inventory used, and overall inventory of an event. If there are no dirt, log, grass, sand, or snow blocks in the voxels, it sets the biome as \"underground\". It then identifies other blocks that were not present in the voxels or inventory, lists nearby entities, and lists completed and failed tasks, while handling cases where they might be empty. Finally, it filters out optional inventory items if required.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/agents/curriculum.py\":149-182", + "content": " equipment = event[\"status\"][\"equipment\"]\n inventory_used = event[\"status\"][\"inventoryUsed\"]\n inventory = event[\"inventory\"]\n if not any(\n \"dirt\" in block\n or \"log\" in block\n or \"grass\" in block\n or \"sand\" in block\n or \"snow\" in block\n for block in voxels\n ):\n biome = \"underground\"\n other_blocks = \", \".join(\n list(\n set(block_records).difference(set(voxels).union(set(inventory.keys())))\n )\n )\n other_blocks = other_blocks if other_blocks else \"None\"\n nearby_entities = (\n \", \".join([k for k, v in sorted(entities.items(), key=lambda x: x[1])])\n if entities\n else \"None\"\n )\n completed_tasks = (\n \", \".join(self.completed_tasks) if self.completed_tasks else \"None\"\n )\n failed_tasks = \", \".join(self.failed_tasks) if self.failed_tasks else \"None\"\n # filter out optional inventory items if required" + }, + { + "comment": "The code checks if the agent's progress is less than the warm-up optional inventory items. If so, it filters the inventory based on core item regex and prepares an observation dictionary containing various information such as context, biome, time of day, nearby blocks, other blocks, nearby entities, health, hunger, position, equipment, and inventory (filtered). The observation dictionary is formatted with descriptive strings for each field.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/agents/curriculum.py\":183-201", + "content": " if self.progress < self.warm_up[\"optional_inventory_items\"]:\n inventory = {\n k: v\n for k, v in inventory.items()\n if self._core_inv_items_regex.search(k) is not None\n }\n observation = {\n \"context\": \"\",\n \"biome\": f\"Biome: {biome}\\n\\n\",\n \"time\": f\"Time: {time_of_day}\\n\\n\",\n \"nearby_blocks\": f\"Nearby blocks: {', '.join(voxels) if voxels else 'None'}\\n\\n\",\n \"other_blocks\": f\"Other blocks that are recently seen: {other_blocks}\\n\\n\",\n \"nearby_entities\": f\"Nearby entities: {nearby_entities}\\n\\n\",\n \"health\": f\"Health: {health:.1f}/20\\n\\n\",\n \"hunger\": f\"Hunger: {hunger:.1f}/20\\n\\n\",\n \"position\": f\"Position: x={position['x']:.1f}, y={position['y']:.1f}, z={position['z']:.1f}\\n\\n\",\n \"equipment\": f\"Equipment: {equipment}\\n\\n\",\n \"inventory\": f\"Inventory ({inventory_used}/36): {inventory if inventory else 'Empty'}\\n\\n\"," + }, + { + "comment": "This code generates an observation dictionary containing information about completed tasks, failed tasks, and contextual questions with answers. If the progress is past the warm-up phase, it adds up to 5 relevant questions and answers to the observation's \"context\" field.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/agents/curriculum.py\":202-227", + "content": " \"chests\": chest_observation,\n \"completed_tasks\": f\"Completed tasks so far: {completed_tasks}\\n\\n\",\n \"failed_tasks\": f\"Failed tasks that are too hard: {failed_tasks}\\n\\n\",\n }\n return observation\n def render_human_message(self, *, events, chest_observation):\n content = \"\"\n observation = self.render_observation(\n events=events, chest_observation=chest_observation\n )\n if self.progress >= self.warm_up[\"context\"]:\n questions, answers = self.run_qa(\n events=events, chest_observation=chest_observation\n )\n i = 1\n for question, answer in zip(questions, answers):\n if \"Answer: Unknown\" in answer or \"language model\" in answer:\n continue\n observation[\"context\"] += f\"Question {i}: {question}\\n\"\n observation[\"context\"] += f\"{answer}\\n\\n\"\n i += 1\n if i > 5:\n break\n for key in self.curriculum_observations:" + }, + { + "comment": "The code defines a Curriculum Agent that generates human messages based on progress, warm-up keys, and observation data. It also proposes tasks depending on progress mode and inventory status. If the progress is zero and mode is \"auto\", it suggests mining one wood log. When inventory is almost full, it checks chest observation to propose tasks related to available chests.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/agents/curriculum.py\":228-250", + "content": " if self.progress >= self.warm_up[key]:\n if self.warm_up[key] != 0:\n should_include = random.random() < 0.8\n else:\n should_include = True\n if should_include:\n content += observation[key]\n print(f\"\\033[35m****Curriculum Agent human message****\\n{content}\\033[0m\")\n return HumanMessage(content=content)\n def propose_next_task(self, *, events, chest_observation, max_retries=5):\n if self.progress == 0 and self.mode == \"auto\":\n task = \"Mine 1 wood log\"\n context = \"You can mine one of oak, birch, spruce, jungle, acacia, dark oak, or mangrove logs.\"\n return task, context\n # hard code task when inventory is almost full\n inventoryUsed = events[-1][1][\"status\"][\"inventoryUsed\"]\n if inventoryUsed >= 33:\n if chest_observation != \"Chests: None\\n\\n\":\n chests = chest_observation[8:-2].split(\"\\n\")\n for chest in chests:" + }, + { + "comment": "This code checks the content of a chest and, if it's empty or contains unknown items, creates a task to deposit useless items into it. The user should have 20 occupied slots after depositing, and can use `bot.inventoryUsed()` to check inventory usage.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/agents/curriculum.py\":251-263", + "content": " content = chest.split(\":\")[1]\n if content == \" Unknown items inside\" or content == \" Empty\":\n position = chest.split(\":\")[0]\n task = f\"Deposit useless items into the chest at {position}\"\n context = (\n f\"Your inventory have {inventoryUsed} occupied slots before depositing. \"\n \"After depositing, your inventory should only have 20 occupied slots. \"\n \"You should deposit useless items such as andesite, dirt, cobblestone, etc. \"\n \"Also, you can deposit low-level tools, \"\n \"For example, if you have a stone pickaxe, you can deposit a wooden pickaxe. \"\n \"Make sure the list of useless items are in your inventory \"\n \"(do not list items already in the chest), \"\n \"You can use bot.inventoryUsed() to check how many inventory slots are used.\"" + }, + { + "comment": "The code checks if a chest is in the inventory or nearby blocks. If it's present, it sets the task as \"Place a chest\" and provides instructions for placing it around the player. If not, it sets the task as \"Craft 1 chest\" and gives instructions on how to craft one using planks of any wood. The code then generates messages and based on the mode (\"auto\" or \"manual\"), returns the next task proposal or manual task respectively. An error is raised for an invalid mode.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/agents/curriculum.py\":264-289", + "content": " )\n return task, context\n if \"chest\" in events[-1][1][\"inventory\"]:\n task = \"Place a chest\"\n context = (\n f\"You have a chest in inventory, place it around you. \"\n f\"If chests is not None, or nearby blocks contains chest, this task is success.\"\n )\n else:\n task = \"Craft 1 chest\"\n context = \"Craft 1 chest with 8 planks of any kind of wood.\"\n return task, context\n messages = [\n self.render_system_message(),\n self.render_human_message(\n events=events, chest_observation=chest_observation\n ),\n ]\n if self.mode == \"auto\":\n return self.propose_next_ai_task(messages=messages, max_retries=max_retries)\n elif self.mode == \"manual\":\n return self.propose_next_manual_task()\n else:\n raise ValueError(f\"Invalid curriculum agent mode: {self.mode}\")" + }, + { + "comment": "The code defines a `propose_next_ai_task` method that retrieves the curriculum from AI and parses it to get the next task. If parsing fails, it retries up to max retries or raises an error if max retries is reached. The code also includes a `parse_ai_message` method that extracts the task from the curriculum message.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/agents/curriculum.py\":291-314", + "content": " def propose_next_ai_task(self, *, messages, max_retries=5):\n if max_retries == 0:\n raise RuntimeError(\"Max retries reached, failed to propose ai task.\")\n curriculum = self.llm(messages).content\n print(f\"\\033[31m****Curriculum Agent ai message****\\n{curriculum}\\033[0m\")\n try:\n response = self.parse_ai_message(curriculum)\n assert \"next_task\" in response\n context = self.get_task_context(response[\"next_task\"])\n return response[\"next_task\"], context\n except Exception as e:\n print(\n f\"\\033[35mError parsing curriculum response: {e}. Trying again!\\033[0m\"\n )\n return self.propose_next_ai_task(\n messages=messages,\n max_retries=max_retries - 1,\n )\n def parse_ai_message(self, message):\n task = \"\"\n for line in message.split(\"\\n\"):\n if line.startswith(\"Task:\"):\n task = line[5:].replace(\".\", \"\").strip()" + }, + { + "comment": "The code snippet contains three functions: \"get_next_task\", \"propose_next_manual_task\", and \"update_exploration_progress\". The first function retrieves the next task from a response, while the second one prompts the user to input a task and context. Lastly, the third function updates progress by checking task completion and adding tasks to completed or failed lists.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/agents/curriculum.py\":315-342", + "content": " assert task, \"Task not found in Curriculum Agent response\"\n return {\"next_task\": task}\n def propose_next_manual_task(self):\n confirmed = False\n task, context = \"\", \"\"\n while not confirmed:\n task = input(\"Enter task: \")\n context = input(\"Enter context: \")\n print(f\"Task: {task}\\nContext: {context}\")\n confirmed = input(\"Confirm? (y/n)\").lower() in [\"y\", \"\"]\n return task, context\n def update_exploration_progress(self, info):\n task = info[\"task\"]\n if task.startswith(\"Deposit useless items into the chest at\"):\n # No need to record the deposit task\n return\n if info[\"success\"]:\n print(f\"\\033[35mCompleted task {task}.\\033[0m\")\n self.completed_tasks.append(task)\n else:\n print(\n f\"\\033[35mFailed to complete task {task}. Skipping to next task.\\033[0m\"\n )\n self.failed_tasks.append(task)\n # clean up tasks and dump to disk" + }, + { + "comment": "The code snippet is responsible for cleaning up tasks, separating completed and failed ones, storing them in separate lists, and then dumping the updated lists into JSON files. It also contains a function to decompose a task given a set of events and generates a SystemMessage with content loaded from a curriculum_task_decomposition prompt.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/agents/curriculum.py\":343-371", + "content": " self.clean_up_tasks()\n def clean_up_tasks(self):\n updated_completed_tasks = []\n # record repeated failed tasks\n updated_failed_tasks = self.failed_tasks\n # dedup but keep order\n for task in self.completed_tasks:\n if task not in updated_completed_tasks:\n updated_completed_tasks.append(task)\n # remove completed tasks from failed tasks\n for task in updated_completed_tasks:\n while task in updated_failed_tasks:\n updated_failed_tasks.remove(task)\n self.completed_tasks = updated_completed_tasks\n self.failed_tasks = updated_failed_tasks\n # dump to json\n U.dump_json(\n self.completed_tasks, f\"{self.ckpt_dir}/curriculum/completed_tasks.json\"\n )\n U.dump_json(self.failed_tasks, f\"{self.ckpt_dir}/curriculum/failed_tasks.json\")\n def decompose_task(self, task, events):\n messages = [\n SystemMessage(\n content=load_prompt(\"curriculum_task_decomposition\")," + }, + { + "comment": "The code is a part of the Curriculum Agent, which decomposes tasks. It constructs messages for LLM and prints information about the final task. The `run_qa` function retrieves new questions from previous ones, checks if the Vectordb collection has any data, and performs a similarity search to find relevant answers with a score below 0.05.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/agents/curriculum.py\":372-396", + "content": " ),\n self.render_human_message(events=events, chest_observation=\"\"),\n HumanMessage(content=f\"Final task: {task}\"),\n ]\n print(\n f\"\\033[31m****Curriculum Agent task decomposition****\\nFinal task: {task}\\033[0m\"\n )\n response = self.llm(messages).content\n print(f\"\\033[31m****Curriculum Agent task decomposition****\\n{response}\\033[0m\")\n return fix_and_parse_json(response)\n def run_qa(self, *, events, chest_observation):\n questions_new, _ = self.run_qa_step1_ask_questions(\n events=events, chest_observation=chest_observation\n )\n questions = []\n answers = []\n for question in questions_new:\n if self.qa_cache_questions_vectordb._collection.count() > 0:\n docs_and_scores = (\n self.qa_cache_questions_vectordb.similarity_search_with_score(\n question, k=1\n )\n )\n if docs_and_scores and docs_and_scores[0][1] < 0.05:" + }, + { + "comment": "Code snippet:\n- Stores and retrieves question-answer pairs in cache\n- Uses run_qa_step2_answer_questions to generate answers from questions\n- Adds new question to vectordb and persists it\n- Dumps qa_cache into json file at specified location\n- Checks lengths of questions, answers, and questions_new are equal\n- Tool ore in question may prompt GPT to use touch enhancement tool for mining", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/agents/curriculum.py\":397-417", + "content": " question_cached = docs_and_scores[0][0].page_content\n assert question_cached in self.qa_cache\n answer_cached = self.qa_cache[question_cached]\n questions.append(question_cached)\n answers.append(answer_cached)\n continue\n answer = self.run_qa_step2_answer_questions(question=question)\n assert question not in self.qa_cache\n self.qa_cache[question] = answer\n self.qa_cache_questions_vectordb.add_texts(\n texts=[question],\n )\n U.dump_json(self.qa_cache, f\"{self.ckpt_dir}/curriculum/qa_cache.json\")\n self.qa_cache_questions_vectordb.persist()\n questions.append(question)\n answers.append(answer)\n assert len(questions_new) == len(questions) == len(answers)\n return questions, answers\n def get_task_context(self, task):\n # if include ore in question, gpt will try to use tool with skill touch enhancement to mine" + }, + { + "comment": "This code appears to be part of an AI agent that interacts with a user, likely in the context of a game like Minecraft. The code handles questions from the user and answers them using a caching system. It also seems to involve persistence of question-answer pairs for future reference, as well as rendering messages to guide the interaction.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/agents/curriculum.py\":418-440", + "content": " question = (\n f\"How to {task.replace('_', ' ').replace(' ore', '').replace(' ores', '').replace('.', '').strip().lower()}\"\n f\" in Minecraft?\"\n )\n if question in self.qa_cache:\n answer = self.qa_cache[question]\n else:\n answer = self.run_qa_step2_answer_questions(question=question)\n self.qa_cache[question] = answer\n self.qa_cache_questions_vectordb.add_texts(\n texts=[question],\n )\n U.dump_json(self.qa_cache, f\"{self.ckpt_dir}/curriculum/qa_cache.json\")\n self.qa_cache_questions_vectordb.persist()\n context = f\"Question: {question}\\n{answer}\"\n return context\n def render_system_message_qa_step1_ask_questions(self):\n return SystemMessage(content=load_prompt(\"curriculum_qa_step1_ask_questions\"))\n def render_human_message_qa_step1_ask_questions(self, *, events, chest_observation):\n observation = self.render_observation(\n events=events, chest_observation=chest_observation" + }, + { + "comment": "This code defines two methods: \"render_human_message_qa_step1_ask_questions\" and \"run_qa_step1_ask_questions\". The first method generates a human-readable message from the given events and chest observation. The second method asks three questions related to blocks, items, and mobs in a specific biome in Minecraft using the information from events and chest observation. It then passes these messages to the QA LLM for response, extracts question-concept pairs using regex, and tries to execute it.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/agents/curriculum.py\":441-464", + "content": " )\n content = \"\"\n for key in self.curriculum_observations:\n content += observation[key]\n return HumanMessage(content=content)\n def run_qa_step1_ask_questions(self, *, events, chest_observation):\n biome = events[-1][1][\"status\"][\"biome\"].replace(\"_\", \" \")\n questions = [\n f\"What are the blocks that I can find in the {biome} in Minecraft?\",\n f\"What are the items that I can find in the {biome} in Minecraft?\",\n f\"What are the mobs that I can find in the {biome} in Minecraft?\",\n ]\n concepts = [biome, biome, biome]\n messages = [\n self.render_system_message_qa_step1_ask_questions(),\n self.render_human_message_qa_step1_ask_questions(\n events=events, chest_observation=chest_observation\n ),\n ]\n qa_response = self.qa_llm(messages).content\n try:\n # Regex pattern to extract question and concept pairs\n pattern = r\"Question \\d+: (.+)\\nConcept \\d+: (.+)\"" + }, + { + "comment": "The code extracts question and concept pairs from the text, stores them in separate lists, checks their lengths are equal, extends existing question and concept lists, handles exceptions while parsing curriculum response, renders system message with prompt for QA step 2, creates a human message with the question, and runs QA step 2 to answer questions.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/agents/curriculum.py\":465-489", + "content": " # Extracting all question and concept pairs from the text\n pairs = re.findall(pattern, qa_response)\n # Storing each question and concept in separate lists\n questions_new = [pair[0] for pair in pairs]\n concepts_new = [pair[1] for pair in pairs]\n assert len(questions_new) == len(concepts_new)\n questions.extend(questions_new)\n concepts.extend(concepts_new)\n except Exception as e:\n print(\n f\"\\033[35mError parsing curriculum response for \"\n f\"QA step 1 ask questions: {e}.\\033[0m\"\n )\n return questions, concepts\n def render_system_message_qa_step2_answer_questions(self):\n return SystemMessage(\n content=load_prompt(\"curriculum_qa_step2_answer_questions\")\n )\n def render_human_message_qa_step2_answer_questions(self, question):\n content = f\"Question: {question}\"\n return HumanMessage(content=content)\n def run_qa_step2_answer_questions(self, question):" + }, + { + "comment": "This code renders system and human messages for a QA step, prints the curriculum agent's question, asks the QA language model for an answer, and returns it. The question and answer are printed in different colors for clarity.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/agents/curriculum.py\":490-497", + "content": " messages = [\n self.render_system_message_qa_step2_answer_questions(),\n self.render_human_message_qa_step2_answer_questions(question=question),\n ]\n print(f\"\\033[35mCurriculum Agent Question: {question}\\033[0m\")\n qa_answer = self.qa_llm(messages).content\n print(f\"\\033[31mCurriculum Agent {qa_answer}\\033[0m\")\n return qa_answer" + } + ] +} \ No newline at end of file diff --git a/docs/doc/17cd18d6-66f4-48b9-af17-eb7faede678b.json b/docs/doc/17cd18d6-66f4-48b9-af17-eb7faede678b.json new file mode 100644 index 00000000..3c32eb97 --- /dev/null +++ b/docs/doc/17cd18d6-66f4-48b9-af17-eb7faede678b.json @@ -0,0 +1,30 @@ +{ + "summary": "This code tackles Voyager program FAQs and Mineflayer issues, covering solutions for errors, bots' behavior, installation, and limitations. It discusses using GPT-4 over GPT-3.5 with prompt length configuration, and estimates costs at around 50 USD for 160 iterations, advising to monitor expenses and bot behavior.", + "details": [ + { + "comment": "Code snippet discusses frequently asked questions related to the Voyager program, including connection errors, access token issues, bot behavior, and usage options.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/FAQ.md\":0-11", + "content": "# Frequently Asked Questions\n* [I got connection error after I click on the Azure login link and login to Microsoft account.](#i-got-a-connection-error-after-i-click-on-the-azure-login-link-and-login-to-my-microsoft-account)\n* [I got `KeyError: 'access_token'` after I copied the link](#i-got-keyerror-accesstoken-after-i-copied-the-link)\n* [I got `Subprocess Mineflayer failed to start` error.](#i-got-subprocess-mineflayer-failed-to-start-error)\n* [I saw the bot left and rejoin the game after each task.](#i-saw-the-bot-left-and-rejoin-the-game-after-each-task)\n* [How to show the bot's first-person perspective?](#how-to-show-the-bots-first-person-view)\n* [Can I use GPT-3.5 instead of GPT-4?](#can-i-use-gpt-35-instead-of-gpt-4)\n* [What's the estimated cost of running Voyager?](#whats-the-estimated-cost-of-running-voyager)\n## I got a connection error after I click on the Azure login link and login to my Microsoft account.\nIt's normal that you get a connection refused or 404 error after you log in. Y" + }, + { + "comment": "Code explains that the new URL contains necessary code like \"M.C...\" which should be copied and pasted. It also mentions a reported issue where 'KeyError: 'access_token'' occurs after copying the link, which was resolved by changing Redirect URI Type to \"Web\". If this fails, try original tutorial or it could be Microsoft's bug in reference #80.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/FAQ.md\":11-17", + "content": "ou will still see the new URL in your browser. You just need to copy and paste that link. It should contain things like `code=M.C....` in that link.\n## I got `KeyError: 'access_token'` after I copied the link\nWhile testing Voyager, we use Redirect URI Type: `Public client/native (mobile & desktop)` in the app registration for Azure Login. However, according to the report in issue [#34](https://github.com/MineDojo/Voyager/issues/34#issuecomment-1567007133), the URI Type was changed to \"Web\" and it resolved the problem. Feel free to attempt both URI Types to determine which one works for you. If all the approaches fail, please refer to the original tutorial in [minecraft-launcher-lib](https://minecraft-launcher-lib.readthedocs.io/en/stable/tutorial/microsoft_login.html).\nUpdate: This is probably a Microsoft's bug. See [issue #80 in minecraft-launcher-lib](https://codeberg.org/JakobDev/minecraft-launcher-lib/issues/80). If you cannot solve this problem, you can try to use the [Minecraft Offi" + }, + { + "comment": "The code provides solutions to resolve issues with Mineflayer, a bot used in the Voyager program. The first issue is ensuring proper installation of Node.js and dependencies, which can be checked by running a command in the specified directory. The second solution involves verifying the correct installation of Fabric and selecting the appropriate version in the Minecraft launcher. The third solution addresses running multiple instances of Voyager by manually changing the port during initialization.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/FAQ.md\":17-37", + "content": "cial Launcher](./installation/minecraft_instance_install.md#option-2-minecraft-official-launcher) and use mc_port to run.\n## I got `Subprocess Mineflayer failed to start` error.\nThere are many reasons that may cause this problem. You can try with following solutions:\n1. Make sure you install nodejs and the dependency packages correctly. You can use the following command to check your installation:\n ```bash\n cd voyager/env/mineflayer\n node index.js\n ```\n If you see `Server started on port {PORT}`, then your installation is correct. You can kill the process by `Ctrl+C`.\n2. Make sure you install Fabric correctly. You should be able to select the Fabric version in the Minecraft launcher. \n3. Each Mineflayer process can only listen to one port. If you want to start multiple instances of `Voyager`, you need to manually change the port when initialization:\n ```python\n from voyager import Voyager\n voyager = Voyager(\n server_port=3001, # default is 3000\n ...\n )\n ```\n## I saw the bot left and rejoin the game after each task." + }, + { + "comment": "Explanation: The code provides information about the necessity of resetting the environment after completing tasks to sync Mineflayer with the Minecraft game, and how to show the bot's first-person view. It also mentions the limitations of Mineflayer in directly getting the bot's view and a potential alternative (prismarine-viewer), but opts not to use it due to video quality issues. The demo video is generated by replay-mod, which records and renders the view from the bot after playing for hours.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/FAQ.md\":39-44", + "content": "After completing each task, we'll reset the environment, which means the bot will exit and rejoin the game. This reset is necessary to synchronize Mineflayer with the Minecraft game. We do this because certain commands we utilize might result in lag on the Mineflayer side, causing the inventory stored in Mineflayer to differ from the actual inventory in the game. However, if you wish to avoid the reset, you can use `voyager.learn(reset_env=False)` and consider increasing the `env_wait_ticks` value. This will provide Mineflayer with additional time to sync with the Minecraft game.\n## How to show the bot's first-person view?\nDue to the Mineflayer's limitation, we currently can not directly get the bot's view in the game. Although there's a plugin called [prismarine-viewer](https://github.com/PrismarineJS/prismarine-viewer), the video quality is not good enough, so we opt not to use it. Our demo video is generated by [replay-mod](https://www.replaymod.com/). We start the recording and let the bot play for hours, then come back to the recording and render the view from the bot." + }, + { + "comment": "The code provides information about using GPT-4 instead of GPT-3.5, mentioning the benefits and the need to configure prompt length when using GPT-3.5. It also estimates the cost of running Voyager at approximately 50 USD for 160 iterations with GPT-4, advising to monitor expenses and bot behavior.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/FAQ.md\":47-53", + "content": "## Can I use GPT-3.5 instead of GPT-4?\nIt's highly recommended to use GPT-4. GPT-3.5 falls behind in terms of code quality and reasoning ability compared to GPT-4. Moreover, GPT-3.5 has a limited context length, which means it may provide incomplete responses. If you insist on using GPT-3.5, it is essential to configure it with `skill_manager_retrieval_top_k` \u2264 2 to reduce the context length of the prompt.\n## What's the estimated cost of running Voyager?\nUsing Voyager for approximately 160 iterations using GPT-4 will cost you around 50 USD. It's important to keep a close eye on your OpenAI API expenses and avoid unnecessary spending. Once Voyager begins running, it's recommended to monitor the bot's behavior for a period and ensure that it successfully completes some tasks." + } + ] +} \ No newline at end of file diff --git a/docs/doc/19969603-9a2d-4b86-9e15-3ef527ebcb47.json b/docs/doc/19969603-9a2d-4b86-9e15-3ef527ebcb47.json new file mode 100644 index 00000000..c1925a73 --- /dev/null +++ b/docs/doc/19969603-9a2d-4b86-9e15-3ef527ebcb47.json @@ -0,0 +1,15 @@ +{ + "summary": "This function checks inputs and retrieves the nearest entity with given name, throwing an error if not found. It handles mob killing and item collection in a game using auto-attack or regular attack based on equipped weapon, waiting for the mob to be removed before collecting items. If successful, progress is saved as \"${mobName}_killed\".", + "details": [ + { + "comment": "This function takes a bot, mob name and timeout as inputs. It checks if both the mobName is a string and timeout is a number. If not, it throws an error. Then, it retrieves the nearest entity with the given mobName, ensuring it's within a certain distance. If no entity is found, it sends a chat message and increments the fail count; if the fail count exceeds 10, it throws an error.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/control_primitives/killMob.js\":0-31", + "content": "async function killMob(bot, mobName, timeout = 300) {\n // return if mobName is not string\n if (typeof mobName !== \"string\") {\n throw new Error(`mobName for killMob must be a string`);\n }\n // return if timeout is not number\n if (typeof timeout !== \"number\") {\n throw new Error(`timeout for killMob must be a number`);\n }\n const weaponsForShooting = [\n \"bow\",\n \"crossbow\",\n \"snowball\",\n \"ender_pearl\",\n \"egg\",\n \"splash_potion\",\n \"trident\",\n ];\n const mainHandItem = bot.inventory.slots[bot.getEquipmentDestSlot(\"hand\")];\n const entity = bot.nearestEntity(\n (entity) =>\n entity.name === mobName &&\n // kill mob distance should be slightly bigger than explore distance\n entity.position.distanceTo(bot.entity.position) < 48\n );\n if (!entity) {\n bot.chat(`No ${mobName} nearby, please explore first`);\n _killMobFailCount++;\n if (_killMobFailCount > 10) {\n throw new Error(" + }, + { + "comment": "This code handles the killing of a mob and subsequent item collection in a game. It uses auto-attack or regular attack based on the weapon equipped, then waits for the mob to be removed before collecting any dropped items. If successful, it saves the progress as \"${mobName}_killed\".", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/control_primitives/killMob.js\":32-50", + "content": " `killMob failed too many times, make sure you explore before calling killMob`\n );\n }\n return;\n }\n let droppedItem;\n if (mainHandItem && weaponsForShooting.includes(mainHandItem.name)) {\n bot.hawkEye.autoAttack(entity, mainHandItem.name);\n droppedItem = await waitForMobShot(bot, entity, timeout);\n } else {\n await bot.pvp.attack(entity);\n droppedItem = await waitForMobRemoved(bot, entity, timeout);\n }\n if (droppedItem) {\n await bot.collectBlock.collect(droppedItem, { ignoreNoPath: true });\n }\n bot.save(`${mobName}_killed`);\n}" + } + ] +} \ No newline at end of file diff --git a/docs/doc/203b39a2-3514-43ef-b408-152548aabab8.json b/docs/doc/203b39a2-3514-43ef-b408-152548aabab8.json new file mode 100644 index 00000000..b1992c54 --- /dev/null +++ b/docs/doc/203b39a2-3514-43ef-b408-152548aabab8.json @@ -0,0 +1,20 @@ +{ + "summary": "This Minecraft assistant suggests tasks based on player progress, avoiding building shelters and specifying task types to avoid. The code provides instructions for suggesting appropriate tasks based on game state.", + "details": [ + { + "comment": "This code describes a Minecraft assistant that provides the next task based on the player's learning progress, considering their position, health, hunger, equipment, inventory, and completed/failed tasks.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/prompts/curriculum.txt\":0-25", + "content": "You are a helpful assistant that tells me the next immediate task to do in Minecraft. My ultimate goal is to discover as many diverse things as possible, accomplish as many diverse tasks as possible and become the best Minecraft player in the world.\nI will give you the following information:\nQuestion 1: ...\nAnswer: ...\nQuestion 2: ...\nAnswer: ...\nQuestion 3: ...\nAnswer: ...\n...\nBiome: ...\nTime: ...\nNearby blocks: ...\nOther blocks that are recently seen: ...\nNearby entities (nearest to farthest): ...\nHealth: Higher than 15 means I'm healthy.\nHunger: Higher than 15 means I'm not hungry.\nPosition: ...\nEquipment: If I have better armor in my inventory, you should ask me to equip it.\nInventory (xx/36): ...\nChests: You can ask me to deposit or take items from these chests. There also might be some unknown chest, you should ask me to open and check items inside the unknown chest.\nCompleted tasks so far: ...\nFailed tasks that are too hard: ...\nYou must follow the following criteria:\n1) You should act as a mentor and guide me to the next task based on my current learning progress." + }, + { + "comment": "This code provides guidelines for the format of tasks, specifying to be concise, single-phrased, and not too difficult or repetitive. It encourages exploring, resource collection, and equipment upgrades while avoiding building shelters.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/prompts/curriculum.txt\":26-31", + "content": "2) Please be very specific about what resources I need to collect, what I need to craft, or what mobs I need to kill.\n3) The next task should follow a concise format, such as \"Mine [quantity] [block]\", \"Craft [quantity] [item]\", \"Smelt [quantity] [item]\", \"Kill [quantity] [mob]\", \"Cook [quantity] [food]\", \"Equip [item]\" etc. It should be a single phrase. Do not propose multiple tasks at the same time. Do not mention anything else.\n4) The next task should not be too hard since I may not have the necessary resources or have learned enough skills to complete it yet.\n5) The next task should be novel and interesting. I should look for rare resources, upgrade my equipment and tools using better materials, and discover new things. I should not be doing the same thing over and over again.\n6) I may sometimes need to repeat some tasks if I need to collect more resources to complete more difficult tasks. Only repeat tasks if necessary.\n7) Do not ask me to build or dig shelter even if it's at night. I want to explore the world and discover new things. I don't want to stay in one place." + }, + { + "comment": "The code provides instructions to avoid certain task types, mentions example of bad tasks, and specifies a response format for suggesting appropriate tasks based on the current game state.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/prompts/curriculum.txt\":32-41", + "content": "8) Tasks that require information beyond the player's status to verify should be avoided. For instance, \"Placing 4 torches\" and \"Dig a 2x1x2 hole\" are not ideal since they require visual confirmation from the screen. All the placing, building, planting, and trading tasks should be avoided. Do not propose task starting with these keywords.\nYou should only respond in the format as described below:\nRESPONSE FORMAT:\nReasoning: Based on the information I listed above, do reasoning about what the next task should be.\nTask: The next task.\nHere's an example response:\nReasoning: The inventory is empty now, chop down a tree to get some wood.\nTask: Obtain a wood log." + } + ] +} \ No newline at end of file diff --git a/docs/doc/243d959b-1db6-4368-a332-1fe3d743b9d1.json b/docs/doc/243d959b-1db6-4368-a332-1fe3d743b9d1.json new file mode 100644 index 00000000..6e5adeeb --- /dev/null +++ b/docs/doc/243d959b-1db6-4368-a332-1fe3d743b9d1.json @@ -0,0 +1,80 @@ +{ + "summary": "This code offers utility functions for file system operations, including path manipulation and directory traversal, file/directory handling (f_remove, f_copy), wildcard support, error handling, verbose option, file paths & directories management, checksums, tarball creation with compression, utility file handling, NFS-safe lock implementation, and options to load/dump text or pickle data by lines.", + "details": [ + { + "comment": "This code file contains various utility functions for working with the file system, such as getting hostname and host ID, opening files in UTF-8 format, checking if an object is a sequence, and packing variable arguments into a list. These functions can be used across different modules within the project for file operations.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/utils/file_utils.py\":0-64", + "content": "\"\"\"\nFile system utils.\n\"\"\"\nimport collections\nimport os\nimport pickle\nimport sys\nimport errno\nimport shutil\nimport glob\n# import pwd\nimport codecs\nimport hashlib\nimport tarfile\nimport fnmatch\nimport tempfile\nfrom datetime import datetime\nfrom socket import gethostname\nimport logging\nf_ext = os.path.splitext\nf_size = os.path.getsize\nis_file = os.path.isfile\nis_dir = os.path.isdir\nget_dir = os.path.dirname\ndef host_name():\n \"Get host name, alias with ``socket.gethostname()``\"\n return gethostname()\ndef host_id():\n \"\"\"\n Returns: first part of hostname up to '.'\n \"\"\"\n return host_name().split(\".\")[0]\ndef utf_open(fname, mode):\n \"\"\"\n Wrapper for codecs.open\n \"\"\"\n return codecs.open(fname, mode=mode, encoding=\"utf-8\")\ndef is_sequence(obj):\n \"\"\"\n Returns:\n True if the sequence is a collections.Sequence and not a string.\n \"\"\"\n return isinstance(obj, collections.abc.Sequence) and not isinstance(obj, str)\ndef pack_varargs(args):\n \"\"\"\n Pack *args or a single list arg as list\n def f(*args):" + }, + { + "comment": "The code defines a series of utility functions for file manipulation. It includes functions like `f_not_empty` which checks if the given file exists and has a non-zero size, `f_expand` to expand special symbols in file paths, `f_exists` to check if a file path exists, and `f_join` to join file paths while expanding special symbols. The code also uses `pack_varargs` for packing arguments into a list format.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/utils/file_utils.py\":65-106", + "content": " arg_list = pack_varargs(args)\n # arg_list is now packed as a list\n \"\"\"\n assert isinstance(args, tuple), \"please input the tuple `args` as in *args\"\n if len(args) == 1 and is_sequence(args[0]):\n return args[0]\n else:\n return args\ndef f_not_empty(*fpaths):\n \"\"\"\n Returns:\n True if and only if the file exists and file size > 0\n if fpath is a dir, if and only if dir exists and has at least 1 file\n \"\"\"\n fpath = f_join(*fpaths)\n if not os.path.exists(fpath):\n return False\n if os.path.isdir(fpath):\n return len(os.listdir(fpath)) > 0\n else:\n return os.path.getsize(fpath) > 0\ndef f_expand(fpath):\n return os.path.expandvars(os.path.expanduser(fpath))\ndef f_exists(*fpaths):\n return os.path.exists(f_join(*fpaths))\ndef f_join(*fpaths):\n \"\"\"\n join file paths and expand special symbols like `~` for home dir\n \"\"\"\n fpaths = pack_varargs(fpaths)\n fpath = f_expand(os.path.join(*fpaths))\n if isinstance(fpath, str):\n fpath = fpath.strip()" + }, + { + "comment": "This function, f_listdir, takes in one or more file paths as arguments and returns a list of files in the specified directories. It can filter files based on their extension or through a user-defined function, sort the file names alphabetically, return full paths to the dir contents, handle non-existent directories gracefully, and optionally use os.walk for recursive directory traversal. However, filter_ext and filter cannot be used together.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/utils/file_utils.py\":107-135", + "content": " return fpath\ndef f_listdir(\n *fpaths,\n filter_ext=None,\n filter=None,\n sort=True,\n full_path=False,\n nonexist_ok=True,\n recursive=False,\n):\n \"\"\"\n Args:\n full_path: True to return full paths to the dir contents\n filter: function that takes in file name and returns True to include\n nonexist_ok: True to return [] if the dir is non-existent, False to raise\n sort: sort the file names by alphabetical\n recursive: True to use os.walk to recursively list files. Note that `filter`\n will be applied to the relative path string to the root dir.\n e.g. filter will take \"a/data1.txt\" and \"a/b/data3.txt\" as input, instead of\n just the base file names \"data1.txt\" and \"data3.txt\".\n if False, will simply call os.listdir()\n \"\"\"\n assert not (filter_ext and filter), \"filter_ext and filter are mutually exclusive\"\n dir_path = f_join(*fpaths)\n if not os.path.exists(dir_path) and nonexist_ok:\n return []\n if recursive:" + }, + { + "comment": "The code defines three functions: \"f_list\", \"f_mkdir\", and \"last_part_in_path\". \n\"f_list\" takes a root directory and optional filter or sort parameters, and returns a list of files in the directory. It can also return absolute paths if the 'full_path' parameter is set to True. \n\"f_mkdir\" recursively creates all subdirectories leading up to a specified file path, creating them only if they do not already exist. \n\"last_part_in_path\" returns the last part of a given file path by splitting it using '/'.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/utils/file_utils.py\":136-176", + "content": " files = [\n os.path.join(os.path.relpath(root, dir_path), file)\n for root, _, files in os.walk(dir_path)\n for file in files\n ]\n else:\n files = os.listdir(dir_path)\n if filter is not None:\n files = [f for f in files if filter(f)]\n elif filter_ext is not None:\n files = [f for f in files if f.endswith(filter_ext)]\n if sort:\n files.sort()\n if full_path:\n return [os.path.join(dir_path, f) for f in files]\n else:\n return files\ndef f_mkdir(*fpaths):\n \"\"\"\n Recursively creates all the subdirs\n If exist, do nothing.\n \"\"\"\n fpath = f_join(*fpaths)\n os.makedirs(fpath, exist_ok=True)\n return fpath\ndef f_mkdir_in_path(*fpaths):\n \"\"\"\n fpath is a file,\n recursively creates all the parent dirs that lead to the file\n If exist, do nothing.\n \"\"\"\n os.makedirs(get_dir(f_join(*fpaths)), exist_ok=True)\ndef last_part_in_path(fpath):\n \"\"\"\n https://stackoverflow.com/questions/3925096/how-to-get-only-the-last-part-of-a-path-in-python" + }, + { + "comment": "The code defines various file utility functions. It checks if a path is absolute or relative, retrieves file modification time, appends suffix to file name retaining its extension, adds an extension if not already present, tests if the file has a specific extension, and globally searches for files recursively.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/utils/file_utils.py\":177-226", + "content": " \"\"\"\n return os.path.basename(os.path.normpath(f_expand(fpath)))\ndef is_abs_path(*fpath):\n return os.path.isabs(f_join(*fpath))\ndef is_relative_path(*fpath):\n return not is_abs_path(f_join(*fpath))\ndef f_time(*fpath):\n \"File modification time\"\n return str(os.path.getctime(f_join(*fpath)))\ndef f_append_before_ext(fpath, suffix):\n \"\"\"\n Append a suffix to file name and retain its extension\n \"\"\"\n name, ext = f_ext(fpath)\n return name + suffix + ext\ndef f_add_ext(fpath, ext):\n \"\"\"\n Append an extension if not already there\n Args:\n ext: will add a preceding `.` if doesn't exist\n \"\"\"\n if not ext.startswith(\".\"):\n ext = \".\" + ext\n if fpath.endswith(ext):\n return fpath\n else:\n return fpath + ext\ndef f_has_ext(fpath, ext):\n \"Test if file path is a text file\"\n _, actual_ext = f_ext(fpath)\n return actual_ext == \".\" + ext.lstrip(\".\")\ndef f_glob(*fpath):\n return glob.glob(f_join(*fpath), recursive=True)\ndef f_remove(*fpath, verbose=False, dry_run=False):" + }, + { + "comment": "The code defines two functions: `f_remove` and `f_copy`. `f_remove` removes a file or directory specified by the given path, supporting wildcards. It prints a dry-run message if in that mode, and handles different OSError scenarios to remove either directories or files. `f_copy` copies files or directories from one location to another, supporting wildcards and ignoring/including certain files as specified by `ignore` and `include`. If any OSError occurs while copying a directory, it attempts to copy the file instead. If verbose is True, it prints success messages for each deletion or copy action.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/utils/file_utils.py\":227-261", + "content": " \"\"\"\n If exist, remove. Supports both dir and file. Supports glob wildcard.\n \"\"\"\n assert isinstance(verbose, bool)\n fpath = f_join(fpath)\n if dry_run:\n print(\"Dry run, delete:\", fpath)\n return\n for f in glob.glob(fpath):\n try:\n shutil.rmtree(f)\n except OSError as e:\n if e.errno == errno.ENOTDIR:\n try:\n os.remove(f)\n except: # final resort safeguard\n pass\n if verbose:\n print(f'Deleted \"{fpath}\"')\ndef f_copy(fsrc, fdst, ignore=None, include=None, exists_ok=True, verbose=False):\n \"\"\"\n Supports both dir and file. Supports glob wildcard.\n \"\"\"\n fsrc, fdst = f_expand(fsrc), f_expand(fdst)\n for f in glob.glob(fsrc):\n try:\n f_copytree(f, fdst, ignore=ignore, include=include, exist_ok=exists_ok)\n except OSError as e:\n if e.errno == errno.ENOTDIR:\n shutil.copy(f, fdst)\n else:\n raise\n if verbose:" + }, + { + "comment": "This code defines a function called `_f_copytree` that copies files and directories from one location to another, allowing for existing destination files and directories if the `exist_ok` parameter is set to True. It also includes functionality to handle symbolic links, ignoring certain names specified by an optional `ignore` function, and using the `os`, `shutil`, and `path` modules.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/utils/file_utils.py\":262-298", + "content": " print(f'Copied \"{fsrc}\" to \"{fdst}\"')\ndef _f_copytree(\n src,\n dst,\n symlinks=False,\n ignore=None,\n exist_ok=True,\n copy_function=shutil.copy2,\n ignore_dangling_symlinks=False,\n):\n \"\"\"Copied from python standard lib shutil.copytree\n except that we allow exist_ok\n Use f_copytree as entry\n \"\"\"\n names = os.listdir(src)\n if ignore is not None:\n ignored_names = ignore(src, names)\n else:\n ignored_names = set()\n os.makedirs(dst, exist_ok=exist_ok)\n errors = []\n for name in names:\n if name in ignored_names:\n continue\n srcname = os.path.join(src, name)\n dstname = os.path.join(dst, name)\n try:\n if os.path.islink(srcname):\n linkto = os.readlink(srcname)\n if symlinks:\n # We can't just leave it to `copy_function` because legacy\n # code with a custom `copy_function` may rely on copytree\n # doing the right thing.\n os.symlink(linkto, dstname)" + }, + { + "comment": "This code handles copying files and directories between two locations. If symlinks are ignored, it uses shutil.copystat to preserve file permissions. If a dangling symlink is encountered and ignore_dangling_symlinks flag is on, the code continues to the next file or directory. It uses _f_copytree for copying directories recursively and calls copy_function for files. Errors are caught and handled to continue with other files.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/utils/file_utils.py\":299-318", + "content": " shutil.copystat(srcname, dstname, follow_symlinks=not symlinks)\n else:\n # ignore dangling symlink if the flag is on\n if not os.path.exists(linkto) and ignore_dangling_symlinks:\n continue\n # otherwise let the copy occurs. copy2 will raise an error\n if os.path.isdir(srcname):\n _f_copytree(\n srcname, dstname, symlinks, ignore, exist_ok, copy_function\n )\n else:\n copy_function(srcname, dstname)\n elif os.path.isdir(srcname):\n _f_copytree(srcname, dstname, symlinks, ignore, exist_ok, copy_function)\n else:\n # Will raise a SpecialFileError for unsupported file types\n copy_function(srcname, dstname)\n # catch the Error from the recursive copytree so that we can\n # continue with other files\n except shutil.Error as err:" + }, + { + "comment": "The code defines a function _include_patterns that creates and returns a function _ignore_patterns to be used with shutil.copytree(). It takes glob-style patterns as arguments, and determines what files to NOT ignore in the file hierarchy rooted at the source directory when copying using shutil.copystat(). Errors during file operations are handled by raising an Error if any occur.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/utils/file_utils.py\":319-349", + "content": " errors.extend(err.args[0])\n except OSError as why:\n errors.append((srcname, dstname, str(why)))\n try:\n shutil.copystat(src, dst)\n except OSError as why:\n # Copying file access times may fail on Windows\n if getattr(why, \"winerror\", None) is None:\n errors.append((src, dst, str(why)))\n if errors:\n raise shutil.Error(errors)\n return dst\ndef _include_patterns(*patterns):\n \"\"\"Factory function that can be used with copytree() ignore parameter.\n Arguments define a sequence of glob-style patterns\n that are used to specify what files to NOT ignore.\n Creates and returns a function that determines this for each directory\n in the file hierarchy rooted at the source directory when used with\n shutil.copytree().\n \"\"\"\n def _ignore_patterns(path, names):\n keep = set(\n name for pattern in patterns for name in fnmatch.filter(names, pattern)\n )\n ignore = set(\n name\n for name in names" + }, + { + "comment": "This code includes functions for copying, moving and splitting file paths. The `f_copytree` function copies a source directory to a destination, ignoring certain patterns. The `f_move` function moves files from one location to another. Lastly, the `f_split_path` function splits a file path into a list of its component folders.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/utils/file_utils.py\":350-386", + "content": " if name not in keep and not os.path.isdir(os.path.join(path, name))\n )\n return ignore\n return _ignore_patterns\ndef f_copytree(fsrc, fdst, symlinks=False, ignore=None, include=None, exist_ok=True):\n fsrc, fdst = f_expand(fsrc), f_expand(fdst)\n assert (ignore is None) or (\n include is None\n ), \"ignore= and include= are mutually exclusive\"\n if ignore:\n ignore = shutil.ignore_patterns(*ignore)\n elif include:\n ignore = _include_patterns(*include)\n _f_copytree(fsrc, fdst, ignore=ignore, symlinks=symlinks, exist_ok=exist_ok)\ndef f_move(fsrc, fdst):\n fsrc, fdst = f_expand(fsrc), f_expand(fdst)\n for f in glob.glob(fsrc):\n shutil.move(f, fdst)\ndef f_split_path(fpath, normpath=True):\n \"\"\"\n Splits path into a list of its component folders\n Args:\n normpath: call os.path.normpath to remove redundant '/' and\n up-level references like \"..\"\n \"\"\"\n if normpath:\n fpath = os.path.normpath(fpath)\n allparts = []\n while 1:" + }, + { + "comment": "This code defines several functions to deal with file paths and directories. The `get_allparts()` function splits a file path into its directory and base name, handling both absolute and relative paths. The `get_script_dir()`, `get_script_file_name()`, and `get_script_self_path()` functions return the directory, filename, and absolute path of the current script, respectively. Lastly, the `get_parent_dir()` function takes a location (directory or file) and returns its parent directory's path, either relative or absolute depending on the optional argument.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/utils/file_utils.py\":387-430", + "content": " parts = os.path.split(fpath)\n if parts[0] == fpath: # sentinel for absolute paths\n allparts.insert(0, parts[0])\n break\n elif parts[1] == fpath: # sentinel for relative paths\n allparts.insert(0, parts[1])\n break\n else:\n fpath = parts[0]\n allparts.insert(0, parts[1])\n return allparts\ndef get_script_dir():\n \"\"\"\n Returns: the dir of current script\n \"\"\"\n return os.path.dirname(os.path.realpath(sys.argv[0]))\ndef get_script_file_name():\n \"\"\"\n Returns: the dir of current script\n \"\"\"\n return os.path.basename(sys.argv[0])\ndef get_script_self_path():\n \"\"\"\n Returns: the dir of current script\n \"\"\"\n return os.path.realpath(sys.argv[0])\ndef get_parent_dir(location, abspath=False):\n \"\"\"\n Args:\n location: current directory or file\n Returns:\n parent directory absolute or relative path\n \"\"\"\n _path = os.path.abspath if abspath else os.path.relpath\n return _path(f_join(location, os.pardir))" + }, + { + "comment": "The code provides functions for calculating an MD5 checksum of a file and creating a tarball. The `md5_checksum` function takes a file path as input and returns the MD5 hash of its contents in hexdigest format. The `create_tar` function creates a tarball from either a source file or folder, optionally compressing it with gzip, bzip2, or xz, and allows for including or excluding specific files based on patterns. If any include or ignore patterns are provided, the source files are temporarily copied to a new directory before being compressed into the tarball.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/utils/file_utils.py\":433-462", + "content": "def md5_checksum(*fpath):\n \"\"\"\n File md5 signature\n \"\"\"\n hash_md5 = hashlib.md5()\n with open(f_join(*fpath), \"rb\") as f:\n for chunk in iter(lambda: f.read(65536), b\"\"):\n hash_md5.update(chunk)\n return hash_md5.hexdigest()\ndef create_tar(fsrc, output_tarball, include=None, ignore=None, compress_mode=\"gz\"):\n \"\"\"\n Args:\n fsrc: source file or folder\n output_tarball: output tar file name\n compress_mode: \"gz\", \"bz2\", \"xz\" or \"\" (empty for uncompressed write)\n include: include pattern, will trigger copy to temp directory\n ignore: ignore pattern, will trigger copy to temp directory\n \"\"\"\n fsrc, output_tarball = f_expand(fsrc), f_expand(output_tarball)\n assert compress_mode in [\"gz\", \"bz2\", \"xz\", \"\"]\n src_base = os.path.basename(fsrc)\n tempdir = None\n if include or ignore:\n tempdir = tempfile.mkdtemp()\n tempdest = f_join(tempdir, src_base)\n f_copy(fsrc, tempdest, include=include, ignore=ignore)\n fsrc = tempdest" + }, + { + "comment": "Code snippet is from Voyager project and contains three functions: 'create_tar', 'extract_tar' and 'move_with_backup'.\n\n1. 'create_tar' creates a tar archive with the option to specify compression mode, source path, and the base name for the archive. It also removes the temporary directory if it exists.\n2. 'extract_tar' extracts members from the specified tar archive into a destination directory, allowing specification of members to extract.\n3. 'move_with_backup' ensures that a file or path is not occupied by renaming existing files with a suffix and recursively backs up everything in the directory.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/utils/file_utils.py\":464-495", + "content": " with tarfile.open(output_tarball, \"w:\" + compress_mode) as tar:\n tar.add(fsrc, arcname=src_base)\n if tempdir:\n f_remove(tempdir)\ndef extract_tar(source_tarball, output_dir=\".\", members=None):\n \"\"\"\n Args:\n source_tarball: extract members from archive\n output_dir: default to current working dir\n members: must be a subset of the list returned by getmembers()\n \"\"\"\n source_tarball, output_dir = f_expand(source_tarball), f_expand(output_dir)\n with tarfile.open(source_tarball, \"r:*\") as tar:\n tar.extractall(output_dir, members=members)\ndef move_with_backup(*fpath, suffix=\".bak\"):\n \"\"\"\n Ensures that a path is not occupied. If there is a file, rename it by\n adding @suffix. Resursively backs up everything.\n Args:\n fpath: file path to clear\n suffix: Add to backed up files (default: {'.bak'})\n \"\"\"\n fpath = str(f_join(*fpath))\n if os.path.exists(fpath):\n move_with_backup(fpath + suffix)\n shutil.move(fpath, fpath + suffix)" + }, + { + "comment": "This code contains several utility functions for file operations, including inserting a string before the extension of a file name, generating timestamp-based file names, implementing a NFS-safe file lock using flufl.lock, loading and dumping pickles, and loading text files. The code also includes some logging setup for the flufl.lock module.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/utils/file_utils.py\":498-537", + "content": "def insert_before_ext(name, insert):\n \"\"\"\n log.txt -> log.ep50.txt\n \"\"\"\n name, ext = os.path.splitext(name)\n return name + insert + ext\ndef timestamp_file_name(fname):\n timestr = datetime.now().strftime(\"_%H-%M-%S_%m-%d-%y\")\n return insert_before_ext(fname, timestr)\ndef get_file_lock(*fpath, timeout: int = 15, logging_level=\"critical\"):\n \"\"\"\n NFS-safe filesystem-backed lock. `pip install flufl.lock`\n https://flufllock.readthedocs.io/en/stable/apiref.html\n Args:\n fpath: should be a path on NFS so that every process can see it\n timeout: seconds\n \"\"\"\n from flufl.lock import Lock\n logging.getLogger(\"flufl.lock\").setLevel(logging_level.upper())\n return Lock(f_join(*fpath), lifetime=timeout)\ndef load_pickle(*fpaths):\n with open(f_join(*fpaths), \"rb\") as fp:\n return pickle.load(fp)\ndef dump_pickle(data, *fpaths):\n with open(f_join(*fpaths), \"wb\") as fp:\n pickle.dump(data, fp)\ndef load_text(*fpaths, by_lines=False):\n with open(f_join(*fpaths), \"r\") as fp:" + }, + { + "comment": "The code defines various functions for loading and dumping text or pickle data, providing options to read by lines or not. It also includes alias names to maintain consistency with other load_* and dump_* functions in the codebase.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/utils/file_utils.py\":538-567", + "content": " if by_lines:\n return fp.readlines()\n else:\n return fp.read()\ndef load_text_lines(*fpaths):\n return load_text(*fpaths, by_lines=True)\ndef dump_text(s, *fpaths):\n with open(f_join(*fpaths), \"w\") as fp:\n fp.write(s)\ndef dump_text_lines(lines: list[str], *fpaths, add_newline=True):\n with open(f_join(*fpaths), \"w\") as fp:\n for line in lines:\n print(line, file=fp, end=\"\\n\" if add_newline else \"\")\n# aliases to be consistent with other load_* and dump_*\npickle_load = load_pickle\npickle_dump = dump_pickle\ntext_load = load_text\nread_text = load_text\nread_text_lines = load_text_lines\nwrite_text = dump_text\nwrite_text_lines = dump_text_lines\ntext_dump = dump_text" + } + ] +} \ No newline at end of file diff --git a/docs/doc/38c132bc-34c1-4902-8db0-e1246f2cbab2.json b/docs/doc/38c132bc-34c1-4902-8db0-e1246f2cbab2.json new file mode 100644 index 00000000..919e4537 --- /dev/null +++ b/docs/doc/38c132bc-34c1-4902-8db0-e1246f2cbab2.json @@ -0,0 +1,15 @@ +{ + "summary": "The givePlacedItemBack function, using parameters bot, name, and position, disables doTileDrops, calls givePlacedItemBackSingle for each pair of name and position, enables doTileDrops again. The givePlacedItemBackSingle gives item to the bot and updates history with block positions within a certain radius. This code destroys blocks, updates history, waits 20 ticks, and returns.", + "details": [ + { + "comment": "Function givePlacedItemBack takes in a bot, name, and position as parameters. It disables doTileDrops, iterates through each pair of name and position, calls givePlacedItemBackSingle for each pair, then enables doTileDrops again. The givePlacedItemBackSingle function gives the item to the bot and checks if any blocks with the same name are within a certain radius, adding their positions to history if not already included.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/control_primitives/givePlacedItemBack.js\":0-23", + "content": "async function givePlacedItemBack(bot, name, position) {\n await bot.chat(\"/gamerule doTileDrops false\");\n // iterate name and position\n const history = [];\n for (let i = 0; i < name.length; i++) {\n await givePlacedItemBackSingle(bot, name[i], position[i]);\n }\n await bot.chat(\"/gamerule doTileDrops true\");\n async function givePlacedItemBackSingle(bot, name, position) {\n bot.chat(`/give bot ${name} 1`);\n const x = Math.floor(position.x);\n const y = Math.floor(position.y);\n const z = Math.floor(position.z);\n // loop through 125 blocks around the block\n const size = 3;\n for (let dx = -size; dx <= size; dx++) {\n for (let dy = -size; dy <= size; dy++) {\n for (let dz = -size; dz <= size; dz++) {\n const block = bot.blockAt(new Vec3(x + dx, y + dy, z + dz));\n if (\n block?.name === name &&\n !history.includes(block.position)\n ) {" + }, + { + "comment": "This code sets a block at a specific location to air and destroys it if the condition is met, updates the history with the block's position, waits for 20 ticks, and returns.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/control_primitives/givePlacedItemBack.js\":24-37", + "content": " await bot.chat(\n `/setblock ${x + dx} ${y + dy} ${\n z + dz\n } air destroy`\n );\n history.push(block.position);\n await bot.waitForTicks(20);\n return;\n }\n }\n }\n }\n }\n}" + } + ] +} \ No newline at end of file diff --git a/docs/doc/40212d79-a1d1-48a5-b4cf-0f0cc553d58d.json b/docs/doc/40212d79-a1d1-48a5-b4cf-0f0cc553d58d.json new file mode 100644 index 00000000..b5789e94 --- /dev/null +++ b/docs/doc/40212d79-a1d1-48a5-b4cf-0f0cc553d58d.json @@ -0,0 +1,10 @@ +{ + "summary": "Kill a pig and collect the dropped item function called \"killMob\" which uses bot's nearestEntity to find the target mob, then attacks it and moves towards its position using pathfinder.", + "details": [ + { + "comment": "Kill a pig and collect the dropped item function called \"killMob\" which uses bot's nearestEntity to find the target mob, then attacks it and moves towards its position using pathfinder.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/control_primitives_context/killMob.js\":0-11", + "content": "// Kill a pig and collect the dropped item: killMob(bot, \"pig\", 300);\nasync function killMob(bot, mobName, timeout = 300) {\n const entity = bot.nearestEntity(\n (entity) =>\n entity.name === mobName &&\n entity.position.distanceTo(bot.entity.position) < 32\n );\n await bot.pvp.attack(entity);\n await bot.pathfinder.goto(\n new GoalBlock(entity.position.x, entity.position.y, entity.position.z)\n );\n}" + } + ] +} \ No newline at end of file diff --git a/docs/doc/41e08461-0e66-44ae-b95a-c9629b1dbbbf.json b/docs/doc/41e08461-0e66-44ae-b95a-c9629b1dbbbf.json new file mode 100644 index 00000000..73eec588 --- /dev/null +++ b/docs/doc/41e08461-0e66-44ae-b95a-c9629b1dbbbf.json @@ -0,0 +1,15 @@ +{ + "summary": "The code contains functions to iterate through items to deposit, find items in bot's inventory, and deposit them into chests. It also checks items inside specific chests using bot's actions for chest interactions.", + "details": [ + { + "comment": "Gets a torch from the chest at position (30, 65, 100) and moves bot to it, opens the chest, retrieves the torch item, closes the chest, and returns.\n\nDeposits a torch into the chest at position (30, 65, 100) and moves bot to it, opens the chest, deposits the torch item, closes the chest, and returns.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/control_primitives_context/useChest.js\":0-18", + "content": "// Get a torch from chest at (30, 65, 100): getItemFromChest(bot, new Vec3(30, 65, 100), {\"torch\": 1});\n// This function will work no matter how far the bot is from the chest.\nasync function getItemFromChest(bot, chestPosition, itemsToGet) {\n await moveToChest(bot, chestPosition);\n const chestBlock = bot.blockAt(chestPosition);\n const chest = await bot.openContainer(chestBlock);\n for (const name in itemsToGet) {\n const itemByName = mcData.itemsByName[name];\n const item = chest.findContainerItem(itemByName.id);\n await chest.withdraw(item.type, null, itemsToGet[name]);\n }\n await closeChest(bot, chestBlock);\n}\n// Deposit a torch into chest at (30, 65, 100): depositItemIntoChest(bot, new Vec3(30, 65, 100), {\"torch\": 1});\n// This function will work no matter how far the bot is from the chest.\nasync function depositItemIntoChest(bot, chestPosition, itemsToDeposit) {\n await moveToChest(bot, chestPosition);\n const chestBlock = bot.blockAt(chestPosition);\n const chest = await bot.openContainer(chestBlock);" + }, + { + "comment": "The code contains a function that iterates through itemsToDeposit, finds the item in bot's inventory, and deposits it into the chest. It also includes another function for checking items inside a specific chest using bot's actions. The functions work together to perform actions related to chest interactions.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/control_primitives_context/useChest.js\":19-34", + "content": " for (const name in itemsToDeposit) {\n const itemByName = mcData.itemsByName[name];\n const item = bot.inventory.findInventoryItem(itemByName.id);\n await chest.deposit(item.type, null, itemsToDeposit[name]);\n }\n await closeChest(bot, chestBlock);\n}\n// Check the items inside the chest at (30, 65, 100): checkItemInsideChest(bot, new Vec3(30, 65, 100));\n// You only need to call this function once without any action to finish task of checking items inside the chest.\nasync function checkItemInsideChest(bot, chestPosition) {\n await moveToChest(bot, chestPosition);\n const chestBlock = bot.blockAt(chestPosition);\n await bot.openContainer(chestBlock);\n // You must close the chest after opening it if you are asked to open a chest\n await closeChest(bot, chestBlock);\n}" + } + ] +} \ No newline at end of file diff --git a/docs/doc/48d6f05b-46f8-4a61-b518-273feb43e29b.json b/docs/doc/48d6f05b-46f8-4a61-b518-273feb43e29b.json new file mode 100644 index 00000000..bdfb721b --- /dev/null +++ b/docs/doc/48d6f05b-46f8-4a61-b518-273feb43e29b.json @@ -0,0 +1,30 @@ +{ + "summary": "The CriticAgent class generates system and human messages, evaluates agent status based on mode (\"manual\" or \"auto\"), and checks task success using user or AI input. It is part of the Voyager project.", + "details": [ + { + "comment": "The code defines a class named `CriticAgent`, which initializes a ChatOpenAI language model and provides methods for rendering system and human messages based on events, task, context, and chest observation. The agent's mode can be set to \"auto\" or \"manual\".", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/agents/critic.py\":0-31", + "content": "from voyager.prompts import load_prompt\nfrom voyager.utils.json_utils import fix_and_parse_json\nfrom langchain.chat_models import ChatOpenAI\nfrom langchain.schema import HumanMessage, SystemMessage\nclass CriticAgent:\n def __init__(\n self,\n model_name=\"gpt-3.5-turbo\",\n temperature=0,\n request_timout=120,\n mode=\"auto\",\n ):\n self.llm = ChatOpenAI(\n model_name=model_name,\n temperature=temperature,\n request_timeout=request_timout,\n )\n assert mode in [\"auto\", \"manual\"]\n self.mode = mode\n def render_system_message(self):\n system_message = SystemMessage(content=load_prompt(\"critic\"))\n return system_message\n def render_human_message(self, *, events, task, context, chest_observation):\n assert events[-1][0] == \"observe\", \"Last event must be observe\"\n biome = events[-1][1][\"status\"][\"biome\"]\n time_of_day = events[-1][1][\"status\"][\"timeOfDay\"]\n voxels = events[-1][1][\"voxels\"]\n health = events[-1][1][\"status\"][\"health\"]" + }, + { + "comment": "This code is capturing the agent's current status and events to construct an observation for evaluation. It checks for errors, retrieves biome, time of day, nearby blocks, health, hunger, and position information from events, and formats them into a string.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/agents/critic.py\":32-59", + "content": " hunger = events[-1][1][\"status\"][\"food\"]\n position = events[-1][1][\"status\"][\"position\"]\n equipment = events[-1][1][\"status\"][\"equipment\"]\n inventory_used = events[-1][1][\"status\"][\"inventoryUsed\"]\n inventory = events[-1][1][\"inventory\"]\n for i, (event_type, event) in enumerate(events):\n if event_type == \"onError\":\n print(f\"\\033[31mCritic Agent: Error occurs {event['onError']}\\033[0m\")\n return None\n observation = \"\"\n observation += f\"Biome: {biome}\\n\\n\"\n observation += f\"Time: {time_of_day}\\n\\n\"\n if voxels:\n observation += f\"Nearby blocks: {', '.join(voxels)}\\n\\n\"\n else:\n observation += f\"Nearby blocks: None\\n\\n\"\n observation += f\"Health: {health:.1f}/20\\n\\n\"\n observation += f\"Hunger: {hunger:.1f}/20\\n\\n\"\n observation += f\"Position: x={position['x']:.1f}, y={position['y']:.1f}, z={position['z']:.1f}\\n\\n\"\n observation += f\"Equipment: {equipment}\\n\\n\"" + }, + { + "comment": "The code snippet contains two functions: \"human_check_task_success\" and \"ai_check_task_success\". The first function prompts the user to confirm if a task is successful and also asks for their critique. It repeats until the user confirms their response. The second function automatically determines whether a task was successful or not, based on the input provided by the AI system. Both functions return either True or False for success, along with the user's critique.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/agents/critic.py\":61-90", + "content": " if inventory:\n observation += f\"Inventory ({inventory_used}/36): {inventory}\\n\\n\"\n else:\n observation += f\"Inventory ({inventory_used}/36): Empty\\n\\n\"\n observation += chest_observation\n observation += f\"Task: {task}\\n\\n\"\n if context:\n observation += f\"Context: {context}\\n\\n\"\n else:\n observation += f\"Context: None\\n\\n\"\n print(f\"\\033[31m****Critic Agent human message****\\n{observation}\\033[0m\")\n return HumanMessage(content=observation)\n def human_check_task_success(self):\n confirmed = False\n success = False\n critique = \"\"\n while not confirmed:\n success = input(\"Success? (y/n)\")\n success = success.lower() == \"y\"\n critique = input(\"Enter your critique:\")\n print(f\"Success: {success}\\nCritique: {critique}\")\n confirmed = input(\"Confirm? (y/n)\") in [\"y\", \"\"]\n return success, critique\n def ai_check_task_success(self, messages, max_retries=5):" + }, + { + "comment": "The code checks the Critic Agent's response and tries to parse it as JSON. If parsing fails, it prints an error message and retries up to a specified number of times. If the \"success\" field is not present in the parsed response or if \"critique\" is missing, default values are set before returning success and critique.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/agents/critic.py\":91-118", + "content": " if max_retries == 0:\n print(\n \"\\033[31mFailed to parse Critic Agent response. Consider updating your prompt.\\033[0m\"\n )\n return False, \"\"\n if messages[1] is None:\n return False, \"\"\n critic = self.llm(messages).content\n print(f\"\\033[31m****Critic Agent ai message****\\n{critic}\\033[0m\")\n try:\n response = fix_and_parse_json(critic)\n assert response[\"success\"] in [True, False]\n if \"critique\" not in response:\n response[\"critique\"] = \"\"\n return response[\"success\"], response[\"critique\"]\n except Exception as e:\n print(f\"\\033[31mError parsing critic response: {e} Trying again!\\033[0m\")\n return self.ai_check_task_success(\n messages=messages,\n max_retries=max_retries - 1,\n )\n def check_task_success(\n self, *, events, task, context, chest_observation, max_retries=5\n ):\n human_message = self.render_human_message(" + }, + { + "comment": "This code is a part of the critic agent class in the Voyager project. It initializes an instance with given events, task, context, and chest_observation. Then it creates a list of messages containing system and human messages. If the mode is set to \"manual\", it calls the human_check_task_success() function, otherwise if the mode is \"auto\", it calls ai_check_task_success() with the messages and max_retries as arguments. For any other invalid mode, it raises a ValueError.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/agents/critic.py\":119-137", + "content": " events=events,\n task=task,\n context=context,\n chest_observation=chest_observation,\n )\n messages = [\n self.render_system_message(),\n human_message,\n ]\n if self.mode == \"manual\":\n return self.human_check_task_success()\n elif self.mode == \"auto\":\n return self.ai_check_task_success(\n messages=messages, max_retries=max_retries\n )\n else:\n raise ValueError(f\"Invalid critic agent mode: {self.mode}\")" + } + ] +} \ No newline at end of file diff --git a/docs/doc/4ecf8571-1b15-42d2-8d08-f9e9c6cf4335.json b/docs/doc/4ecf8571-1b15-42d2-8d08-f9e9c6cf4335.json new file mode 100644 index 00000000..78fbed53 --- /dev/null +++ b/docs/doc/4ecf8571-1b15-42d2-8d08-f9e9c6cf4335.json @@ -0,0 +1,15 @@ +{ + "summary": "This code uses the exploreUntil function to search for iron ore underground and pigs on the surface, with exploration stopping after 60 seconds or upon meeting a callback condition. It is within a Voyager control function and may define/manipulate exploration-related primitives.", + "details": [ + { + "comment": "This code uses the exploreUntil function to find an iron ore underground and a pig on the surface. It uses Vec3 direction vectors for searching, with exploration stopping after 60 seconds or if the callback condition is met early. The exploreUntil function takes parameters such as bot, direction, maxTime, and callback for exploration control.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/control_primitives_context/exploreUntil.js\":0-28", + "content": "/*\nExplore until find an iron_ore, use Vec3(0, -1, 0) because iron ores are usually underground\nawait exploreUntil(bot, new Vec3(0, -1, 0), 60, () => {\n const iron_ore = bot.findBlock({\n matching: mcData.blocksByName[\"iron_ore\"].id,\n maxDistance: 32,\n });\n return iron_ore;\n});\nExplore until find a pig, use Vec3(1, 0, 1) because pigs are usually on the surface\nlet pig = await exploreUntil(bot, new Vec3(1, 0, 1), 60, () => {\n const pig = bot.nearestEntity((entity) => {\n return (\n entity.name === \"pig\" &&\n entity.position.distanceTo(bot.entity.position) < 32\n );\n });\n return pig;\n});\n*/\nasync function exploreUntil(bot, direction, maxTime = 60, callback) {\n /*\n Implementation of this function is omitted.\n direction: Vec3, can only contain value of -1, 0 or 1\n maxTime: number, the max time for exploration\n callback: function, early stop condition, will be called each second, exploration will stop if return value is not null\n Return: null if explore timeout, otherwise return the return value of callback" + }, + { + "comment": "This code block appears to be within a function that controls the exploration of the Voyager. The code ends with a closing brace, which may indicate that this section is defining or manipulating some control primitives in the context of exploration.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/control_primitives_context/exploreUntil.js\":29-30", + "content": " */\n}" + } + ] +} \ No newline at end of file diff --git a/docs/doc/54c9fc94-5e8c-4048-9df3-af7c5d9c9a5c.json b/docs/doc/54c9fc94-5e8c-4048-9df3-af7c5d9c9a5c.json new file mode 100644 index 00000000..4fa54940 --- /dev/null +++ b/docs/doc/54c9fc94-5e8c-4048-9df3-af7c5d9c9a5c.json @@ -0,0 +1,20 @@ +{ + "summary": "The function finds the minimum recipe for an item by checking nearby crafting tables and informs users about missing items needed to craft.", + "details": [ + { + "comment": "The function checks if there's a crafting table nearby and finds the recipe with the least missing ingredients for a specific item. It throws an error if no crafting table is found, and iterates through recipes to determine the number of missing ingredients.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/control_primitives/craftHelper.js\":0-25", + "content": "function failedCraftFeedback(bot, name, item, craftingTable) {\n const recipes = bot.recipesAll(item.id, null, craftingTable);\n if (!recipes.length) {\n throw new Error(`No crafting table nearby`);\n } else {\n const recipes = bot.recipesAll(\n item.id,\n null,\n mcData.blocksByName.crafting_table.id\n );\n // find the recipe with the fewest missing ingredients\n var min = 999;\n var min_recipe = null;\n for (const recipe of recipes) {\n const delta = recipe.delta;\n var missing = 0;\n for (const delta_item of delta) {\n if (delta_item.count < 0) {\n const inventory_item = bot.inventory.findInventoryItem(\n mcData.items[delta_item.id].name,\n null\n );\n if (!inventory_item) {\n missing += -delta_item.count;\n } else {\n missing += Math.max(" + }, + { + "comment": "This code checks if a recipe is missing any items, calculates the minimum missing item count for each ingredient, and stores the corresponding recipe. Then, it iterates through the delta (missing item counts) of each ingredient and checks if any are negative. If an ingredient's count is negative in the bot's inventory, it adds the absolute value of that difference to the message string. If the count is less than the missing count, it updates the minimum missing count and recipe accordingly. Finally, it assigns the minimum recipe delta to the variable 'delta'.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/control_primitives/craftHelper.js\":26-53", + "content": " -delta_item.count - inventory_item.count,\n 0\n );\n }\n }\n }\n if (missing < min) {\n min = missing;\n min_recipe = recipe;\n }\n }\n const delta = min_recipe.delta;\n let message = \"\";\n for (const delta_item of delta) {\n if (delta_item.count < 0) {\n const inventory_item = bot.inventory.findInventoryItem(\n mcData.items[delta_item.id].name,\n null\n );\n if (!inventory_item) {\n message += ` ${-delta_item.count} more ${\n mcData.items[delta_item.id].name\n }, `;\n } else {\n if (inventory_item.count < -delta_item.count) {\n message += `${\n -delta_item.count - inventory_item.count\n } more ${mcData.items[delta_item.id].name}`;" + }, + { + "comment": "The code checks if the bot has the required items to craft a specified item. If it doesn't, it informs the user about the missing items.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/control_primitives/craftHelper.js\":54-60", + "content": " }\n }\n }\n }\n bot.chat(`I cannot make ${name} because I need: ${message}`);\n }\n}" + } + ] +} \ No newline at end of file diff --git a/docs/doc/58c1ea2d-f2f6-4c47-b575-43ad1bb76429.json b/docs/doc/58c1ea2d-f2f6-4c47-b575-43ad1bb76429.json new file mode 100644 index 00000000..92fe28c2 --- /dev/null +++ b/docs/doc/58c1ea2d-f2f6-4c47-b575-43ad1bb76429.json @@ -0,0 +1,10 @@ +{ + "summary": "The code sets up a helper assistant to answer Minecraft-related questions based on provided context and the assistant's own knowledge. It provides guidelines for formatting answers as well, stating to start with \"Answer: \" and indicate \"Answer: Unknown\" if no answer is available.", + "details": [ + { + "comment": "The code sets up a helper assistant to answer Minecraft-related questions based on provided context and the assistant's own knowledge. It provides guidelines for formatting answers as well, stating to start with \"Answer: \" and indicate \"Answer: Unknown\" if no answer is available.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/prompts/curriculum_qa_step2_answer_questions.txt\":0-7", + "content": "You are a helpful assistant that answer my question about Minecraft.\nI will give you the following information:\nQuestion: ...\nYou will answer the question based on the context (only if available and helpful) and your own knowledge of Minecraft.\n1) Start your answer with \"Answer: \".\n2) Answer \"Answer: Unknown\" if you don't know the answer." + } + ] +} \ No newline at end of file diff --git a/docs/doc/602b4f78-6ada-4190-80f6-51242fbcc3ac.json b/docs/doc/602b4f78-6ada-4190-80f6-51242fbcc3ac.json new file mode 100644 index 00000000..4024bc22 --- /dev/null +++ b/docs/doc/602b4f78-6ada-4190-80f6-51242fbcc3ac.json @@ -0,0 +1,10 @@ +{ + "summary": "This function \"shoot\" allows a bot to shoot a valid weapon at a target entity. It checks if the weapon is valid, if it exists in the bot's inventory, and if the target is nearby. If all conditions are met, it auto-attacks using a bow and handles auto_shot_stopped event.", + "details": [ + { + "comment": "This function \"shoot\" allows a bot to shoot a valid weapon at a target entity. It checks if the weapon is valid, if it exists in the bot's inventory, and if the target is nearby. If all conditions are met, it auto-attacks using a bow and handles auto_shot_stopped event.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/control_primitives/shoot.js\":0-33", + "content": "// shoot 1 pig with a bow: shoot(bot, \"bow\", \"pig\");\nasync function shoot(bot, weapon, target) {\n const validWeapons = [\n \"bow\",\n \"crossbow\",\n \"snowball\",\n \"ender_pearl\",\n \"egg\",\n \"splash_potion\",\n \"trident\",\n ];\n if (!validWeapons.includes(weapon)) {\n bot.chat(`${weapon} is not a valid weapon for shooting`);\n return;\n }\n const weaponItem = mcData.itemsByName[weapon];\n if (!bot.inventory.findInventoryItem(weaponItem.id, null)) {\n bot.chat(`No ${weapon} in inventory for shooting`);\n return;\n }\n const targetEntity = bot.nearestEntity(\n (entity) =>\n entity.name === target\n );\n if (!targetEntity) {\n bot.chat(`No ${target} nearby`);\n return;\n }\n bot.hawkEye.autoAttack(targetEntity, \"bow\");\n bot.on('auto_shot_stopped', (target) => {\n })\n}" + } + ] +} \ No newline at end of file diff --git a/docs/doc/654bc7ee-80ca-4e01-9e16-4aba068a8d15.json b/docs/doc/654bc7ee-80ca-4e01-9e16-4aba068a8d15.json new file mode 100644 index 00000000..e3d5b8d8 --- /dev/null +++ b/docs/doc/654bc7ee-80ca-4e01-9e16-4aba068a8d15.json @@ -0,0 +1,15 @@ +{ + "summary": "The function `mineCobblestone` ensures a wooden pickaxe is available, equips it, and explores to find stone blocks, mining 8 cobblestones each time until the maximum count is reached or no more blocks are found.", + "details": [ + { + "comment": "Function: Explore and collect cobblestone using wooden pickaxe\n- Check if wooden pickaxe is in inventory, craft one if not.\n- Equips wooden pickaxe if found, then explores until stone block is found.\n- Collects stone block when found and places it in the inventory.\n- Repeats process until a set number of cobblestones are collected.\n- Stops if maximum cobblestone count reached or if no more stone blocks are found.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/prompts/skill.txt\":0-23", + "content": "You are a helpful assistant that writes a description of the given function written in Mineflayer javascript code.\n1) Do not mention the function name.\n2) Do not mention anything about `bot.chat` or helper functions.\n3) There might be some helper functions before the main function, but you only need to describe the main function.\n4) Try to summarize the function in no more than 6 sentences.\n5) Your response should be a single line of text.\nFor example, if the function is:\nasync function mineCobblestone(bot) {\n // Check if the wooden pickaxe is in the inventory, if not, craft one\n let woodenPickaxe = bot.inventory.findInventoryItem(mcData.itemsByName[\"wooden_pickaxe\"].id);\n if (!woodenPickaxe) {\n bot.chat(\"Crafting a wooden pickaxe.\");\n await craftWoodenPickaxe(bot);\n woodenPickaxe = bot.inventory.findInventoryItem(mcData.itemsByName[\"wooden_pickaxe\"].id);\n }\n // Equip the wooden pickaxe if it exists\n if (woodenPickaxe) {\n await bot.equip(woodenPickaxe, \"hand\");\n // Explore until we find a stone block" + }, + { + "comment": "The function `mineCobblestone` checks for the availability of a wooden pickaxe in the inventory. If not, it crafts one. Afterward, equips the wooden pickaxe in the hand. It then explores until finding a stone block and mines 8 cobblestone blocks from that block. Upon successful completion, the bot informs about the mining event and saves it. If the wooden pickaxe is not available initially, it fails to mine cobblestones.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/prompts/skill.txt\":24-50", + "content": " await exploreUntil(bot, new Vec3(1, -1, 1), 60, () => {\n const stone = bot.findBlock({\n matching: mcData.blocksByName[\"stone\"].id,\n maxDistance: 32\n });\n if (stone) {\n return true;\n }\n });\n // Mine 8 cobblestone blocks using the wooden pickaxe\n bot.chat(\"Found a stone block. Mining 8 cobblestone blocks.\");\n await mineBlock(bot, \"stone\", 8);\n bot.chat(\"Successfully mined 8 cobblestone blocks.\");\n // Save the event of mining 8 cobblestone\n bot.save(\"cobblestone_mined\");\n } else {\n bot.chat(\"Failed to craft a wooden pickaxe. Cannot mine cobblestone.\");\n }\n}\nThe main function is `mineCobblestone`.\nThen you would write:\nThe function is about mining 8 cobblestones using a wooden pickaxe. First check if a wooden pickaxe is in the inventory. If not, craft one. If the wooden pickaxe is available, equip the wooden pickaxe in the hand. Next, explore the environment until finding a stone block. Once a stone block is found, mine a total of 8 cobblestone blocks using the wooden pickaxe." + } + ] +} \ No newline at end of file diff --git a/docs/doc/66d625cd-9415-4ed5-8dbd-2144360734fd.json b/docs/doc/66d625cd-9415-4ed5-8dbd-2144360734fd.json new file mode 100644 index 00000000..1585a024 --- /dev/null +++ b/docs/doc/66d625cd-9415-4ed5-8dbd-2144360734fd.json @@ -0,0 +1,20 @@ +{ + "summary": "This code sets up Azure login for Minecraft Voyager, requiring an Azure portal account and registering a new application with client_id, redirect_url, optional secret_value, and desired Minecraft version specified using fabric-loader.", + "details": [ + { + "comment": "Code snippet provides instructions to set up a Minecraft instance for Voyager, with the recommended method being Microsoft Azure Login. It outlines steps to create an Azure portal account, navigate to Active Directory, and register a new application.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/installation/minecraft_instance_install.md\":0-11", + "content": "# Minecraft Instance Install\nTo start using Voyager, you should first make sure to have an official [Minecraft](https://www.minecraft.net/) game (version 1.19) installed. \nThere are two ways to start a Minecraft instance for Voyager. Sometimes GPT-4 will write an infinite loop that runs forever. In this case, there'll be a request timeout. Using Azure login can automatically resume the running if there's a request timeout.\n## Option 1: Microsoft Azure Login (Recommended)\nUsing this method will allow Voyager to automatically resume when there's a request timeout. This is dependent on the [minecraft-launcher-lib](https://minecraft-launcher-lib.readthedocs.io/en/stable/tutorial/microsoft_login.html#let-the-user-log-in) library.\n1. Sign in to [Azure Portal](https://portal.azure.com/).\n2. Go to [Azure Active Directory](https://portal.azure.com/#blade/Microsoft_AAD_IAM/ActiveDirectoryMenuBlade/Overview).\n3. Click on the `App Registrations` tab on the left panel.\n4. Click on the `New registration` button." + }, + { + "comment": "Fill out the form with provided values, register the application, obtain client ID and secret value, check Minecraft versions for valid version, then collect azure_login information.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/installation/minecraft_instance_install.md\":12-21", + "content": "5. Fill the form with the following values:\n - Name: `YOUR_APP_NAME`\n - Supported account types: `Accounts in any organizational directory (Any Azure AD directory - Multitenant) and personal Microsoft accounts`\n - Redirect URI Type: `Public client/native (mobile & desktop)`, Value: `https://127.0.0.1/auth-response` (If you get `KeyError: 'access_token'` in the end, you can try to change the type to `Web`, see [FAQ](https://github.com/MineDojo/Voyager/blob/main/FAQ.md) for more information)\n6. Click on the `Register` button.\n7. The `Application (client) ID` will be your `client_id`.\n8. [Optional] Go to the `Certificates & Secrets` tab and click on the `New client secret` button. Fill the description by yourself. After you click `Add`, you will see your value, this will be your `secret_value`.\n9. Go to your Minecraft install location `YOUR_MINECRAFT_GAME_LOCATION/versions`, and check all the versions you have. All the folder names are your valid `version` value. \nAfter these steps, you will finally get your azure_login information:" + }, + { + "comment": "This code sets up the necessary parameters for logging into Azure to create an instance of Minecraft. The \"client_id\" refers to a specific ID from Step 7, the \"redirect_url\" is set to \"https://127.0.0.1/auth-response\", the \"secret_value\" is an optional secret key from Step 8, and the \"version\" refers to the Minecraft version desired for use. This specific code utilizes fabric-loader-0.14.18-1.19 to run all experiments.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/installation/minecraft_instance_install.md\":22-40", + "content": "```python\nazure_login = {\n \"client_id\": \"CLIENT_ID FROM STEP 7\",\n \"redirect_url\": \"https://127.0.0.1/auth-response\",\n \"secret_value\": \"[OPTIONAL] SECRET_KEY FROM STEP 8\",\n \"version\": \"MINECRAFT VERSION YOU WANT TO USE\",\n}\n```\n**Voyager use `fabric-loader-0.14.18-1.19` version to run all the experiments.** You may not have this version currently, you can move on to the [Fabric Mods Install](fabric_mods_install.md#fabric-mods-install) section and follow the instructions there to install the fabric version of the game.\n## Option 2: Minecraft Official Launcher\nAfter you install official Minecraft, you should have a Minecraft official launcher, open it, and follow the instructions here:\n1. Select the version you want to play and start the game.\n2. Select `Singleplayer` and create a new world.\n3. Set Game Mode to `Creative` and Difficulty to `Peaceful`.\n4. After the world is created, press `Esc` and select `Open to LAN`.\n5. Select `Allow cheats: ON` and press `Start LAN World`.\n6. You will see a port number in the chat log, that is your `mc-port`, use this number to instantiate Voyager later." + } + ] +} \ No newline at end of file diff --git a/docs/doc/68c8ccff-77f9-45c1-bab9-a3d0df5e550d.json b/docs/doc/68c8ccff-77f9-45c1-bab9-a3d0df5e550d.json new file mode 100644 index 00000000..91d353dc --- /dev/null +++ b/docs/doc/68c8ccff-77f9-45c1-bab9-a3d0df5e550d.json @@ -0,0 +1,10 @@ +{ + "summary": "This function smelts a specified item using another item as fuel in a furnace. It finds the corresponding blocks for the items and furnace, then performs actions to smelt the items. The loop repeats for the specified count of items, waiting 12 seconds between each smelt. Finally, it closes the furnace after completion.", + "details": [ + { + "comment": "This function smelts a specified item using another item as fuel in a furnace. It finds the corresponding blocks for the items and furnace, then performs actions to smelt the items. The loop repeats for the specified count of items, waiting 12 seconds between each smelt. Finally, it closes the furnace after completion.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/control_primitives_context/smeltItem.js\":0-21", + "content": "// Smelt 1 raw_iron into 1 iron_ingot using 1 oak_planks as fuel: smeltItem(bot, \"raw_iron\", \"oak_planks\");\n// You must place a furnace before calling this function\nasync function smeltItem(bot, itemName, fuelName, count = 1) {\n const item = mcData.itemsByName[itemName];\n const fuel = mcData.itemsByName[fuelName];\n const furnaceBlock = bot.findBlock({\n matching: mcData.blocksByName.furnace.id,\n maxDistance: 32,\n });\n await bot.pathfinder.goto(\n new GoalLookAtBlock(furnaceBlock.position, bot.world)\n );\n const furnace = await bot.openFurnace(furnaceBlock);\n for (let i = 0; i < count; i++) {\n await furnace.putFuel(fuel.id, null, 1);\n await furnace.putInput(item.id, null, 1);\n // Wait 12 seconds for the furnace to smelt the item\n await bot.waitForTicks(12 * 20);\n await furnace.takeOutput();\n }\n await furnace.close();\n}" + } + ] +} \ No newline at end of file diff --git a/docs/doc/69107c9d-b002-4292-a652-917d191fe232.json b/docs/doc/69107c9d-b002-4292-a652-917d191fe232.json new file mode 100644 index 00000000..62bb2581 --- /dev/null +++ b/docs/doc/69107c9d-b002-4292-a652-917d191fe232.json @@ -0,0 +1,25 @@ +{ + "summary": "This code generates Minecraft-related prompts based on biome and time, requiring users to follow a specific response format for answering.", + "details": [ + { + "comment": "Code snippet outlines a text file named \"curriculum_qa_step1_ask_questions.txt\" where an AI will provide questions related to Minecraft based on given information like biome, time, nearby blocks, and other relevant factors. The AI should ask at least 5 but not more than 10 specific Minecraft-related questions, adhering to criteria specified in the file.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/prompts/curriculum_qa_step1_ask_questions.txt\":0-23", + "content": "You are a helpful assistant that asks questions to help me decide the next immediate task to do in Minecraft. My ultimate goal is to discover as many things as possible, accomplish as many tasks as possible and become the best Minecraft player in the world.\nI will give you the following information:\nBiome: ...\nTime: ...\nNearby blocks: ...\nOther blocks that are recently seen: ...\nNearby entities (nearest to farthest): ...\nHealth: ...\nHunger: ...\nPosition: ...\nEquipment: ...\nInventory (xx/36): ...\nChests: ...\nCompleted tasks so far: ...\nFailed tasks that are too hard: ...\nYou must follow the following criteria:\n1) You should ask at least 5 questions (but no more than 10 questions) to help me decide the next immediate task to do. Each question should be followed by the concept that the question is about.\n2) Your question should be specific to a concept in Minecraft.\n Bad example (the question is too general):\n Question: What is the best way to play Minecraft?\n Concept: unknown\n Bad example (axe is still general, you should specify the type of axe such as wooden axe):" + }, + { + "comment": "This code is providing examples of good and bad question prompts for the curriculum. Good prompts should be self-contained and not require any context, while bad prompts require additional information such as current biome, inventory, nearby entities or blocks.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/prompts/curriculum_qa_step1_ask_questions.txt\":24-45", + "content": " What are the benefits of using an axe to gather resources?\n Concept: axe\n Good example:\n Question: How to make a wooden pickaxe?\n Concept: wooden pickaxe\n3) Your questions should be self-contained and not require any context.\n Bad example (the question requires the context of my current biome):\n Question: What are the blocks that I can find in my current biome?\n Concept: unknown\n Bad example (the question requires the context of my current inventory):\n Question: What are the resources you need the most currently?\n Concept: unknown\n Bad example (the question requires the context of my current inventory):\n Question: Do you have any gold or emerald resources?\n Concept: gold\n Bad example (the question requires the context of my nearby entities):\n Question: Can you see any animals nearby that you can kill for food?\n Concept: food\n Bad example (the question requires the context of my nearby blocks):\n Question: Is there any water source nearby?\n Concept: water\n Good example:" + }, + { + "comment": "The code provides prompts for asking questions related to specific concepts and tasks within the game. The user can inquire about items, mobs, tasks, or ores found in different biomes, with examples given for sparse jungle, creepers, wooden pickaxes, and various ores.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/prompts/curriculum_qa_step1_ask_questions.txt\":46-67", + "content": " Question: What are the blocks that I can find in the sparse jungle?\n Concept: sparse jungle\n4) Do not ask questions about building tasks (such as building a shelter) since they are too hard for me to do.\nLet's say your current biome is sparse jungle. You can ask questions like:\nQuestion: What are the items that I can find in the sparse jungle?\nConcept: sparse jungle\nQuestion: What are the mobs that I can find in the sparse jungle?\nConcept: sparse jungle\nLet's say you see a creeper nearby, and you have not defeated a creeper before. You can ask a question like:\nQuestion: How to defeat the creeper?\nConcept: creeper\nLet's say your last completed task is \"Craft a wooden pickaxe\". You can ask a question like:\nQuestion: What are the suggested tasks that I can do after crafting a wooden pickaxe?\nConcept: wooden pickaxe\nHere are some more question and concept examples:\nQuestion: What are the ores that I can find in the sparse jungle?\nConcept: sparse jungle\n(the above concept should not be \"ore\" because " + }, + { + "comment": "This code is asking for questions related to specific concepts in the game, and requires looking up information for answers. The responses should follow a specific format, including reasoning and the relevant concept being asked about.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/prompts/curriculum_qa_step1_ask_questions.txt\":67-93", + "content": "I need to look up the page of \"sparse jungle\" to find out what ores I can find in the sparse jungle)\nQuestion: How can you obtain food in the sparse jungle?\nConcept: sparse jungle\n(the above concept should not be \"food\" because I need to look up the page of \"sparse jungle\" to find out what food I can obtain in the sparse jungle)\nQuestion: How can you use the furnace to upgrade your equipment and make useful items?\nConcept: furnace\nQuestion: How to obtain a diamond ore?\nConcept: diamond ore\nQuestion: What are the benefits of using a stone pickaxe over a wooden pickaxe?\nConcept: stone pickaxe\nQuestion: What are the tools that you can craft using wood planks and sticks?\nConcept: wood planks\nYou should only respond in the format as described below:\nRESPONSE FORMAT:\nReasoning: ...\nQuestion 1: ...\nConcept 1: ...\nQuestion 2: ...\nConcept 2: ...\nQuestion 3: ...\nConcept 3: ...\nQuestion 4: ...\nConcept 4: ...\nQuestion 5: ...\nConcept 5: ...\n..." + } + ] +} \ No newline at end of file diff --git a/docs/doc/6b80f1fc-dd49-4070-bc18-3aaf45822bb6.json b/docs/doc/6b80f1fc-dd49-4070-bc18-3aaf45822bb6.json new file mode 100644 index 00000000..4bd69563 --- /dev/null +++ b/docs/doc/6b80f1fc-dd49-4070-bc18-3aaf45822bb6.json @@ -0,0 +1,20 @@ +{ + "summary": "This function enables a bot to smelt items using a furnace, managing inventory and pathfinding, handles errors, and notifies users upon failure.", + "details": [ + { + "comment": "Function to smelt an item using a furnace. Checks if itemName and fuelName are strings, and count is a number. Verifies the existence of items and a nearby furnace. Pathfinding to the furnace and opening it for further processing.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/control_primitives/smeltItem.js\":0-28", + "content": "async function smeltItem(bot, itemName, fuelName, count = 1) {\n // return if itemName or fuelName is not string\n if (typeof itemName !== \"string\" || typeof fuelName !== \"string\") {\n throw new Error(\"itemName or fuelName for smeltItem must be a string\");\n }\n // return if count is not a number\n if (typeof count !== \"number\") {\n throw new Error(\"count for smeltItem must be a number\");\n }\n const item = mcData.itemsByName[itemName];\n const fuel = mcData.itemsByName[fuelName];\n if (!item) {\n throw new Error(`No item named ${itemName}`);\n }\n if (!fuel) {\n throw new Error(`No item named ${fuelName}`);\n }\n const furnaceBlock = bot.findBlock({\n matching: mcData.blocksByName.furnace.id,\n maxDistance: 32,\n });\n if (!furnaceBlock) {\n throw new Error(\"No furnace nearby\");\n } else {\n await bot.pathfinder.goto(\n new GoalLookAtBlock(furnaceBlock.position, bot.world)\n );\n }\n const furnace = await bot.openFurnace(furnaceBlock);" + }, + { + "comment": "The code checks if the bot has the required items (item and fuel) in its inventory, then smelts them one by one in a furnace. It also handles potential errors like invalid fuels or inputs, and closes the furnace after finishing the smelting process.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/control_primitives/smeltItem.js\":29-55", + "content": " let success_count = 0;\n for (let i = 0; i < count; i++) {\n if (!bot.inventory.findInventoryItem(item.id, null)) {\n bot.chat(`No ${itemName} to smelt in inventory`);\n break;\n }\n if (furnace.fuelSeconds < 15 && furnace.fuelItem()?.name !== fuelName) {\n if (!bot.inventory.findInventoryItem(fuel.id, null)) {\n bot.chat(`No ${fuelName} as fuel in inventory`);\n break;\n }\n await furnace.putFuel(fuel.id, null, 1);\n await bot.waitForTicks(20);\n if (!furnace.fuel && furnace.fuelItem()?.name !== fuelName) {\n throw new Error(`${fuelName} is not a valid fuel`);\n }\n }\n await furnace.putInput(item.id, null, 1);\n await bot.waitForTicks(12 * 20);\n if (!furnace.outputItem()) {\n throw new Error(`${itemName} is not a valid input`);\n }\n await furnace.takeOutput();\n success_count++;\n }\n furnace.close();\n if (success_count > 0) bot.chat(`Smelted ${success_count} ${itemName}.`);" + }, + { + "comment": "Else block executed when smelting fails, notifies user to check fuel and input, increments fail count, throws an error after 10 failures for further investigation.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/control_primitives/smeltItem.js\":56-67", + "content": " else {\n bot.chat(\n `Failed to smelt ${itemName}, please check the fuel and input.`\n );\n _smeltItemFailCount++;\n if (_smeltItemFailCount > 10) {\n throw new Error(\n `smeltItem failed too many times, please check the fuel and input.`\n );\n }\n }\n}" + } + ] +} \ No newline at end of file diff --git a/docs/doc/6c211109-1197-4b2a-92eb-eecf9e7ab723.json b/docs/doc/6c211109-1197-4b2a-92eb-eecf9e7ab723.json new file mode 100644 index 00000000..634d598b --- /dev/null +++ b/docs/doc/6c211109-1197-4b2a-92eb-eecf9e7ab723.json @@ -0,0 +1,10 @@ +{ + "summary": "The code specifies the dependencies required for the project. It includes tqdm, langchain, javascript, setuptools, openai, chardet, cchardet, chromadb, tiktoken, requests, gymnasium, psutil, and minecraft_launcher_lib. These are essential libraries needed for the project's functionality.", + "details": [ + { + "comment": "The code specifies the dependencies required for the project. It includes tqdm, langchain, javascript, setuptools, openai, chardet, cchardet, chromadb, tiktoken, requests, gymnasium, psutil, and minecraft_launcher_lib. These are essential libraries needed for the project's functionality.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/requirements.txt\":0-13", + "content": "tqdm\nlangchain\njavascript\nsetuptools\nopenai\nchardet\ncchardet\nchromadb==0.3.29\ntiktoken\nrequests\nsetuptools\ngymnasium\npsutil\nminecraft_launcher_lib" + } + ] +} \ No newline at end of file diff --git a/docs/doc/71bf2928-6fba-4134-8b27-685a5d81fac2.json b/docs/doc/71bf2928-6fba-4134-8b27-685a5d81fac2.json new file mode 100644 index 00000000..147c9f94 --- /dev/null +++ b/docs/doc/71bf2928-6fba-4134-8b27-685a5d81fac2.json @@ -0,0 +1,20 @@ +{ + "summary": "The `exploreUntil` function executes exploration tasks with a maximum time limit, resolving the result or null based on success or timeout.", + "details": [ + { + "comment": "Explore downward for 60 seconds: exploreUntil(bot, new Vec3(0, -1, 0), 60);\n\nThe function `exploreUntil` takes a bot, direction, maximum time (default 60), and a callback. It checks if maxTime is a number, and if the callback is a function. If successful, it returns a resolved promise with the test result. If direction is 0 in all dimensions, it throws an error. If direction values are not -1, 0, or 1, it also throws an error.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/control_primitives/exploreUntil.js\":0-32", + "content": "// Explore downward for 60 seconds: exploreUntil(bot, new Vec3(0, -1, 0), 60);\nasync function exploreUntil(\n bot,\n direction,\n maxTime = 60,\n callback = () => {\n return false;\n }\n) {\n if (typeof maxTime !== \"number\") {\n throw new Error(\"maxTime must be a number\");\n }\n if (typeof callback !== \"function\") {\n throw new Error(\"callback must be a function\");\n }\n const test = callback();\n if (test) {\n bot.chat(\"Explore success.\");\n return Promise.resolve(test);\n }\n if (direction.x === 0 && direction.y === 0 && direction.z === 0) {\n throw new Error(\"direction cannot be 0, 0, 0\");\n }\n if (\n !(\n (direction.x === 0 || direction.x === 1 || direction.x === -1) &&\n (direction.y === 0 || direction.y === 1 || direction.y === -1) &&\n (direction.z === 0 || direction.z === 1 || direction.z === -1)\n )\n ) {\n throw new Error(\n \"direction must be a Vec3 only with value of -1, 0 or 1\"\n );" + }, + { + "comment": "The code sets up an interval-based exploration function that randomly generates new exploration goals within a 20x20x20 block around the current entity position. The maxTime parameter limits the total time spent exploring to either the specified value or 1200 seconds. The function returns a Promise, and includes cleanup functions for clearing the interval and goal when complete or timed out.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/control_primitives/exploreUntil.js\":33-66", + "content": " }\n maxTime = Math.min(maxTime, 1200);\n return new Promise((resolve, reject) => {\n const dx = direction.x;\n const dy = direction.y;\n const dz = direction.z;\n let explorationInterval;\n let maxTimeTimeout;\n const cleanUp = () => {\n clearInterval(explorationInterval);\n clearTimeout(maxTimeTimeout);\n bot.pathfinder.setGoal(null);\n };\n const explore = () => {\n const x =\n bot.entity.position.x +\n Math.floor(Math.random() * 20 + 10) * dx;\n const y =\n bot.entity.position.y +\n Math.floor(Math.random() * 20 + 10) * dy;\n const z =\n bot.entity.position.z +\n Math.floor(Math.random() * 20 + 10) * dz;\n let goal = new GoalNear(x, y, z);\n if (dy === 0) {\n goal = new GoalNearXZ(x, z);\n }\n bot.pathfinder.setGoal(goal);\n try {\n const result = callback();" + }, + { + "comment": "The code performs an exploration task with a maximum time limit. If the exploration succeeds within the specified time, it cleans up and resolves the result. Otherwise, it cleans up, rejects the error, and resolves null. The code sets two intervals: one for exploration at 2-second intervals, and another for timeout after the maximum time in milliseconds.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/control_primitives/exploreUntil.js\":67-86", + "content": " if (result) {\n cleanUp();\n bot.chat(\"Explore success.\");\n resolve(result);\n }\n } catch (err) {\n cleanUp();\n reject(err);\n }\n };\n explorationInterval = setInterval(explore, 2000);\n maxTimeTimeout = setTimeout(() => {\n cleanUp();\n bot.chat(\"Max exploration time reached\");\n resolve(null);\n }, maxTime * 1000);\n });\n}" + } + ] +} \ No newline at end of file diff --git a/docs/doc/79d16ca1-87dd-4431-9f6b-bf81a49fc387.json b/docs/doc/79d16ca1-87dd-4431-9f6b-bf81a49fc387.json new file mode 100644 index 00000000..22979375 --- /dev/null +++ b/docs/doc/79d16ca1-87dd-4431-9f6b-bf81a49fc387.json @@ -0,0 +1,10 @@ +{ + "summary": "This code imports various utility functions and classes from submodules 'file_utils', 'json_utils', and 'record_utils'. These utilities are likely to be used throughout the Voyager module for handling file operations, JSON manipulation, and event recording.", + "details": [ + { + "comment": "This code imports various utility functions and classes from submodules 'file_utils', 'json_utils', and 'record_utils'. These utilities are likely to be used throughout the Voyager module for handling file operations, JSON manipulation, and event recording.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/utils/__init__.py\":0-2", + "content": "from .file_utils import *\nfrom .json_utils import *\nfrom .record_utils import EventRecorder" + } + ] +} \ No newline at end of file diff --git a/docs/doc/7c2459d9-5cdc-4e00-800e-6b86373d3b3a.json b/docs/doc/7c2459d9-5cdc-4e00-800e-6b86373d3b3a.json new file mode 100644 index 00000000..b5627621 --- /dev/null +++ b/docs/doc/7c2459d9-5cdc-4e00-800e-6b86373d3b3a.json @@ -0,0 +1,10 @@ +{ + "summary": "This function mines a specified block (e.g., stone) by finding and collecting the blocks within a 32-distance range, up to the count specified. It ignores \"No Path\" errors during collection.", + "details": [ + { + "comment": "This function mines a specified block (e.g., stone) by finding and collecting the blocks within a 32-distance range, up to the count specified. It ignores \"No Path\" errors during collection.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/control_primitives_context/mineBlock.js\":0-14", + "content": "// Mine 3 cobblestone: mineBlock(bot, \"stone\", 3);\nasync function mineBlock(bot, name, count = 1) {\n const blocks = bot.findBlocks({\n matching: (block) => {\n return block.name === name;\n },\n maxDistance: 32,\n count: count,\n });\n const targets = [];\n for (let i = 0; i < Math.min(blocks.length, count); i++) {\n targets.push(bot.blockAt(blocks[i]));\n }\n await bot.collectBlock.collect(targets, { ignoreNoPath: true });\n}" + } + ] +} \ No newline at end of file diff --git a/docs/doc/7da1ff44-9e80-4ba5-88e6-799292db7881.json b/docs/doc/7da1ff44-9e80-4ba5-88e6-799292db7881.json new file mode 100644 index 00000000..f189dd08 --- /dev/null +++ b/docs/doc/7da1ff44-9e80-4ba5-88e6-799292db7881.json @@ -0,0 +1,45 @@ +{ + "summary": "This code offers JSON handling functions, correcting escapes and braces count imbalance using aliases. It removes non-JSON content before validating with Python's json.loads().", + "details": [ + { + "comment": "The code defines various functions for handling JSON data: `json_load`, `json_loads`, `json_dump`, and `json_dumps`. It also includes aliases for these functions prefixed with \"load_\", \"dump_\", and \"dumps_\". The function `extract_char_position` extracts the character position from a JSONDecodeError error message.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/utils/json_utils.py\":0-43", + "content": "import json\nimport re\nfrom typing import Any, Dict, Union\nfrom .file_utils import f_join\ndef json_load(*file_path, **kwargs):\n file_path = f_join(file_path)\n with open(file_path, \"r\") as fp:\n return json.load(fp, **kwargs)\ndef json_loads(string, **kwargs):\n return json.loads(string, **kwargs)\ndef json_dump(data, *file_path, **kwargs):\n file_path = f_join(file_path)\n with open(file_path, \"w\") as fp:\n json.dump(data, fp, **kwargs)\ndef json_dumps(data, **kwargs):\n \"\"\"\n Returns: string\n \"\"\"\n return json.dumps(data, **kwargs)\n# ---------------- Aliases -----------------\n# add aliases where verb goes first, json_load -> load_json\nload_json = json_load\nloads_json = json_loads\ndump_json = json_dump\ndumps_json = json_dumps\ndef extract_char_position(error_message: str) -> int:\n \"\"\"Extract the character position from the JSONDecodeError message.\n Args:\n error_message (str): The error message from the JSONDecodeError\n exception.\n Returns:\n int: The character position." + }, + { + "comment": "Code snippet includes three functions: \n1. `char_position()` - searches for character position in error message using regex and returns the integer value.\n2. `add_quotes_to_property_names()` - adds quotes to property names in JSON string, ensuring valid syntax.\n3. `balance_braces()` - balances braces in a JSON string to ensure proper syntax.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/utils/json_utils.py\":44-82", + "content": " \"\"\"\n import re\n char_pattern = re.compile(r\"\\(char (\\d+)\\)\")\n if match := char_pattern.search(error_message):\n return int(match[1])\n else:\n raise ValueError(\"Character position not found in the error message.\")\ndef add_quotes_to_property_names(json_string: str) -> str:\n \"\"\"\n Add quotes to property names in a JSON string.\n Args:\n json_string (str): The JSON string.\n Returns:\n str: The JSON string with quotes added to property names.\n \"\"\"\n def replace_func(match):\n return f'\"{match.group(1)}\":'\n property_name_pattern = re.compile(r\"(\\w+):\")\n corrected_json_string = property_name_pattern.sub(replace_func, json_string)\n try:\n json.loads(corrected_json_string)\n return corrected_json_string\n except json.JSONDecodeError as e:\n raise e\ndef balance_braces(json_string: str) -> str:\n \"\"\"\n Balance the braces in a JSON string.\n Args:\n json_string (str): The JSON string.\n Returns:\n str: The JSON string with braces balanced." + }, + { + "comment": "This code fixes common JSON errors by adding missing closing braces, removing invalid escapes and ensures the corrected string is valid JSON. The function \"fix_invalid_escape\" handles removing invalid escape characters, \"correct_json\" adjusts closing brace count imbalance and \"corrected_json_string\" verifies if the string is a valid JSON.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/utils/json_utils.py\":83-119", + "content": " \"\"\"\n open_braces_count = json_string.count(\"{\")\n close_braces_count = json_string.count(\"}\")\n while open_braces_count > close_braces_count:\n json_string += \"}\"\n close_braces_count += 1\n while close_braces_count > open_braces_count:\n json_string = json_string.rstrip(\"}\")\n close_braces_count -= 1\n try:\n json.loads(json_string)\n return json_string\n except json.JSONDecodeError as e:\n raise e\ndef fix_invalid_escape(json_str: str, error_message: str) -> str:\n while error_message.startswith(\"Invalid \\\\escape\"):\n bad_escape_location = extract_char_position(error_message)\n json_str = json_str[:bad_escape_location] + json_str[bad_escape_location + 1 :]\n try:\n json.loads(json_str)\n return json_str\n except json.JSONDecodeError as e:\n error_message = str(e)\n return json_str\ndef correct_json(json_str: str) -> str:\n \"\"\"\n Correct common JSON errors.\n Args:\n json_str (str): The JSON string." + }, + { + "comment": "This code attempts to fix and parse a potentially invalid JSON string. It first tries to load the string using `json.loads()`. If this fails with a `JSONDecodeError`, it checks the error message for specific issues like escape characters or missing quotes, and applies fixes accordingly before retrying to load the string as JSON. If none of these methods succeed, the original JSON string is returned. The function also includes an optional flag to try to fix the string using GPT (Generic Programming Tool), but this feature seems to be unimplemented in the provided code.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/utils/json_utils.py\":120-151", + "content": " \"\"\"\n try:\n json.loads(json_str)\n return json_str\n except json.JSONDecodeError as e:\n error_message = str(e)\n if error_message.startswith(\"Invalid \\\\escape\"):\n json_str = fix_invalid_escape(json_str, error_message)\n if error_message.startswith(\n \"Expecting property name enclosed in double quotes\"\n ):\n json_str = add_quotes_to_property_names(json_str)\n try:\n json.loads(json_str)\n return json_str\n except json.JSONDecodeError as e:\n error_message = str(e)\n if balanced_str := balance_braces(json_str):\n return balanced_str\n return json_str\ndef fix_and_parse_json(\n json_str: str, try_to_fix_with_gpt: bool = True\n) -> Union[str, Dict[Any, Any]]:\n \"\"\"Fix and parse JSON string\"\"\"\n try:\n json_str = json_str.replace(\"\\t\", \"\")\n return json.loads(json_str)\n except json.JSONDecodeError as _: # noqa: F841\n json_str = correct_json(json_str)" + }, + { + "comment": "This code attempts to handle JSON strings where the AI response may contain text before the opening brace. It first tries to parse the original string using json.loads(), then, if an error occurs, it finds the first opening brace, removes any non-JSON text before it, and parses the remaining string as JSON.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/utils/json_utils.py\":152-175", + "content": " try:\n return json.loads(json_str)\n except json.JSONDecodeError as _: # noqa: F841\n pass\n # Let's do something manually:\n # sometimes GPT responds with something BEFORE the braces:\n # \"I'm sorry, I don't understand. Please try again.\"\n # {\"text\": \"I'm sorry, I don't understand. Please try again.\",\n # \"confidence\": 0.0}\n # So let's try to find the first brace and then parse the rest\n # of the string\n try:\n brace_index = json_str.index(\"{\")\n json_str = json_str[brace_index:]\n last_brace_index = json_str.rindex(\"}\")\n json_str = json_str[: last_brace_index + 1]\n return json.loads(json_str)\n except json.JSONDecodeError as e: # noqa: F841\n # if try_to_fix_with_gpt:\n # print(\n # \"Warning: Failed to parse AI output, attempting to fix.\"\n # \"\\n If you see this warning frequently, it's likely that\"\n # \" your prompt is confusing the AI. Try changing it up\"\n # \" slightly.\"" + }, + { + "comment": "The code attempts to fix a JSON string by using the function `fix_json` with a provided schema. If the AI-fixed JSON is not \"failed\", it will be loaded using `json.loads`. If the fix fails, it prints a message and returns the original JSON string. The function utilizes an AI to correct the JSON string according to the given schema.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/utils/json_utils.py\":176-199", + "content": " # )\n # # Now try to fix this up using the ai_functions\n # ai_fixed_json = fix_json(json_str, JSON_SCHEMA)\n #\n # if ai_fixed_json != \"failed\":\n # return json.loads(ai_fixed_json)\n # else:\n # # This allows the AI to react to the error message,\n # # which usually results in it correcting its ways.\n # print(\"Failed to fix ai output, telling the AI.\")\n # return json_str\n # else:\n raise e\n# def fix_json(json_str: str, schema: str) -> str:\n# \"\"\"Fix the given JSON string to make it parseable and fully complient with the provided schema.\"\"\"\n#\n# # Try to fix the JSON using gpt:\n# function_string = \"def fix_json(json_str: str, schema:str=None) -> str:\"\n# args = [f\"'''{json_str}'''\", f\"'''{schema}'''\"]\n# description_string = (\n# \"Fixes the provided JSON string to make it parseable\"\n# \" and fully complient with the provided schema.\\n If an object or\"" + }, + { + "comment": "This code is attempting to fix incorrect JSON format by enclosing it within triple backticks, and then validating the corrected JSON using Python's json.loads() method. If the JSON remains invalid, an error will be raised. This function is designed to handle cases where the input JSON has minor formatting issues.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/utils/json_utils.py\":200-225", + "content": "# \" field specified in the schema isn't contained within the correct\"\n# \" JSON, it is ommited.\\n This function is brilliant at guessing\"\n# \" when the format is incorrect.\"\n# )\n#\n# # If it doesn't already start with a \"`\", add one:\n# if not json_str.startswith(\"`\"):\n# json_str = \"```json\\n\" + json_str + \"\\n```\"\n# result_string = call_ai_function(\n# function_string, args, description_string, model=cfg.fast_llm_model\n# )\n# if cfg.debug:\n# print(\"------------ JSON FIX ATTEMPT ---------------\")\n# print(f\"Original JSON: {json_str}\")\n# print(\"-----------\")\n# print(f\"Fixed JSON: {result_string}\")\n# print(\"----------- END OF FIX ATTEMPT ----------------\")\n#\n# try:\n# json.loads(result_string) # just check the validity\n# return result_string\n# except: # noqa: E722\n# # Get the call stack:\n# # import traceback\n# # call_stack = traceback.format_exc()\n# # print(f\"Failed to fix JSON: '{json_str}' \"+call_stack)" + }, + { + "comment": "This line of code is conditionally returning the string \"failed\" as a result. It's likely part of an if statement or function that evaluates some input and assigns this value based on specific criteria not shown in this snippet.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/utils/json_utils.py\":226-226", + "content": "# return \"failed\"" + } + ] +} \ No newline at end of file diff --git a/docs/doc/82306b7d-b96b-4ec0-baf3-9c0038f8a5a3.json b/docs/doc/82306b7d-b96b-4ec0-baf3-9c0038f8a5a3.json new file mode 100644 index 00000000..30a3b0a1 --- /dev/null +++ b/docs/doc/82306b7d-b96b-4ec0-baf3-9c0038f8a5a3.json @@ -0,0 +1,10 @@ +{ + "summary": "This code is an AI-generated assistant tasked with decomposing a specified Minecraft task into subgoals based on the user's current inventory. It should return a Python list of these subgoals, each in a specific format, including necessary tools at different levels. The response must be in JSON format to ensure proper parsing by Python's `json.loads`.", + "details": [ + { + "comment": "This code is an AI-generated assistant tasked with decomposing a specified Minecraft task into subgoals based on the user's current inventory. It should return a Python list of these subgoals, each in a specific format, including necessary tools at different levels. The response must be in JSON format to ensure proper parsing by Python's `json.loads`.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/prompts/curriculum_task_decomposition.txt\":0-11", + "content": "You are a helpful assistant that generates a curriculum of subgoals to complete any Minecraft task specified by me.\nI'll give you a final task and my current inventory, you need to decompose the task into a list of subgoals based on my inventory.\nYou must follow the following criteria:\n1) Return a Python list of subgoals that can be completed in order to complete the specified task.\n2) Each subgoal should follow a concise format, such as \"Mine [quantity] [block]\", \"Craft [quantity] [item]\", \"Smelt [quantity] [item]\", \"Kill [quantity] [mob]\", \"Cook [quantity] [food]\", \"Equip [item]\".\n3) Include each level of necessary tools as a subgoal, such as wooden, stone, iron, diamond, etc.\nYou should only respond in JSON format as described below:\n[\"subgoal1\", \"subgoal2\", \"subgoal3\", ...]\nEnsure the response can be parsed by Python `json.loads`, e.g.: no trailing commas, no single quotes, etc." + } + ] +} \ No newline at end of file diff --git a/docs/doc/87a0f11b-09b0-413c-997d-921cb033cc76.json b/docs/doc/87a0f11b-09b0-413c-997d-921cb033cc76.json new file mode 100644 index 00000000..69ade981 --- /dev/null +++ b/docs/doc/87a0f11b-09b0-413c-997d-921cb033cc76.json @@ -0,0 +1,15 @@ +{ + "summary": "\"craftItem\" is an asynchronous function that crafts items using a crafting table, validating inputs and catching errors with feedback before throwing an error after 10 tries.", + "details": [ + { + "comment": "This code defines an asynchronous function named \"craftItem\" that crafts a specified item by finding and using a crafting table. It checks if the input name is a string and count is a number before executing, ensuring valid parameters. If valid, it finds the nearest crafting table and attempts to craft the item using the bot's \"craft\" function.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/control_primitives/craftItem.js\":0-29", + "content": "async function craftItem(bot, name, count = 1) {\n // return if name is not string\n if (typeof name !== \"string\") {\n throw new Error(\"name for craftItem must be a string\");\n }\n // return if count is not number\n if (typeof count !== \"number\") {\n throw new Error(\"count for craftItem must be a number\");\n }\n const itemByName = mcData.itemsByName[name];\n if (!itemByName) {\n throw new Error(`No item named ${name}`);\n }\n const craftingTable = bot.findBlock({\n matching: mcData.blocksByName.crafting_table.id,\n maxDistance: 32,\n });\n if (!craftingTable) {\n bot.chat(\"Craft without a crafting table\");\n } else {\n await bot.pathfinder.goto(\n new GoalLookAtBlock(craftingTable.position, bot.world)\n );\n }\n const recipe = bot.recipesFor(itemByName.id, null, 1, craftingTable)[0];\n if (recipe) {\n bot.chat(`I can make ${name}`);\n try {\n await bot.craft(recipe, count, craftingTable);\n bot.chat(`I did the recipe for ${name} ${count} times`);" + }, + { + "comment": "Code block catches errors in crafting recipes and provides feedback for failed attempts, limiting the maximum number of tries to 10 before throwing an error.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/control_primitives/craftItem.js\":30-42", + "content": " } catch (err) {\n bot.chat(`I cannot do the recipe for ${name} ${count} times`);\n }\n } else {\n failedCraftFeedback(bot, name, itemByName, craftingTable);\n _craftItemFailCount++;\n if (_craftItemFailCount > 10) {\n throw new Error(\n \"craftItem failed too many times, check chat log to see what happened\"\n );\n }\n }\n}" + } + ] +} \ No newline at end of file diff --git a/docs/doc/88e7741a-2b92-4edb-be3d-702f3675bc75.json b/docs/doc/88e7741a-2b92-4edb-be3d-702f3675bc75.json new file mode 100644 index 00000000..ecf5cfa7 --- /dev/null +++ b/docs/doc/88e7741a-2b92-4edb-be3d-702f3675bc75.json @@ -0,0 +1,10 @@ +{ + "summary": "Crafts specified item by following the recipe, requires a crafting table nearby.", + "details": [ + { + "comment": "Crafts specified item by following the recipe, requires a crafting table nearby.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/control_primitives_context/craftItem.js\":0-13", + "content": "// Craft 8 oak_planks from 2 oak_log (do the recipe 2 times): craftItem(bot, \"oak_planks\", 2);\n// You must place a crafting table before calling this function\nasync function craftItem(bot, name, count = 1) {\n const item = mcData.itemsByName[name];\n const craftingTable = bot.findBlock({\n matching: mcData.blocksByName.crafting_table.id,\n maxDistance: 32,\n });\n await bot.pathfinder.goto(\n new GoalLookAtBlock(craftingTable.position, bot.world)\n );\n const recipe = bot.recipesFor(item.id, null, 1, craftingTable)[0];\n await bot.craft(recipe, count, craftingTable);\n}" + } + ] +} \ No newline at end of file diff --git a/docs/doc/8ac109af-c2f5-4873-a3ec-f558bd6ffe7c.json b/docs/doc/8ac109af-c2f5-4873-a3ec-f558bd6ffe7c.json new file mode 100644 index 00000000..152594b4 --- /dev/null +++ b/docs/doc/8ac109af-c2f5-4873-a3ec-f558bd6ffe7c.json @@ -0,0 +1,20 @@ +{ + "summary": "The code snippet introduces a reusable Mineflayer assistant using APIs for tasks in Minecraft, with focus on chat, exploration, and event listeners, while ensuring fair gameplay.", + "details": [ + { + "comment": "Code snippet introduces a helpful Mineflayer assistant that utilizes Mineflayer APIs to complete tasks in Minecraft. The code includes pre-written programs, requires an async function, and emphasizes reusing existing programs while collecting blocks with `mineBlock(bot, name, count)`.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/prompts/action_template.txt\":0-31", + "content": "You are a helpful assistant that writes Mineflayer javascript code to complete any Minecraft task specified by me.\nHere are some useful programs written with Mineflayer APIs.\n{programs}\nAt each round of conversation, I will give you\nCode from the last round: ...\nExecution error: ...\nChat log: ...\nBiome: ...\nTime: ...\nNearby blocks: ...\nNearby entities (nearest to farthest):\nHealth: ...\nHunger: ...\nPosition: ...\nEquipment: ...\nInventory (xx/36): ...\nChests: ...\nTask: ...\nContext: ...\nCritique: ...\nYou should then respond to me with\nExplain (if applicable): Are there any steps missing in your plan? Why does the code not complete the task? What does the chat log and execution error imply?\nPlan: How to complete the task step by step. You should pay attention to Inventory since it tells what you have. The task completeness check is also based on your final inventory.\nCode:\n 1) Write an async function taking the bot as the only argument.\n 2) Reuse the above useful programs as much as possible.\n - Use `mineBlock(bot, name, count)` to collect blocks. Do not use `bot.dig` directly." + }, + { + "comment": "Code provides guidelines for crafting, smelting, placing blocks, and killing mobs using specific functions without directly using the respective bot methods. The code emphasizes reusability, avoiding strong assumptions about the inventory, checking for required items before using them, and defining variables inside functions. Functions from previous round are not saved or executed.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/prompts/action_template.txt\":32-38", + "content": " - Use `craftItem(bot, name, count)` to craft items. Do not use `bot.craft` or `bot.recipesFor` directly.\n - Use `smeltItem(bot, name count)` to smelt items. Do not use `bot.openFurnace` directly.\n - Use `placeItem(bot, name, position)` to place blocks. Do not use `bot.placeBlock` directly.\n - Use `killMob(bot, name, timeout)` to kill mobs. Do not use `bot.attack` directly.\n 3) Your function will be reused for building more complex functions. Therefore, you should make it generic and reusable. You should not make strong assumption about the inventory (as it may be changed at a later time), and therefore you should always check whether you have the required items before using them. If not, you should first collect the required items and reuse the above useful programs.\n 4) Functions in the \"Code from the last round\" section will not be saved or executed. Do not reuse functions listed there.\n 5) Anything defined outside a function will be ignored, define all your variables inside your functions." + }, + { + "comment": "Code is providing guidelines for writing code that interacts with a bot, focusing on chat functionality, exploration, and event listener usage. It emphasizes not cheating or using unnecessary functions.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/prompts/action_template.txt\":39-48", + "content": " 6) Call `bot.chat` to show the intermediate progress.\n 7) Use `exploreUntil(bot, direction, maxDistance, callback)` when you cannot find something. You should frequently call this before mining blocks or killing mobs. You should select a direction at random every time instead of constantly using (1, 0, 1).\n 8) `maxDistance` should always be 32 for `bot.findBlocks` and `bot.findBlock`. Do not cheat.\n 9) Do not write infinite loops or recursive functions.\n 10) Do not use `bot.on` or `bot.once` to register event listeners. You definitely do not need them.\n 11) Name your function in a meaningful way (can infer the task from the name).\nYou should only respond in the format as described below:\nRESPONSE FORMAT:\n{response_format}" + } + ] +} \ No newline at end of file diff --git a/docs/doc/97225fa2-8603-4ed2-8728-2e3bf588e67d.json b/docs/doc/97225fa2-8603-4ed2-8728-2e3bf588e67d.json new file mode 100644 index 00000000..add1b18e --- /dev/null +++ b/docs/doc/97225fa2-8603-4ed2-8728-2e3bf588e67d.json @@ -0,0 +1,90 @@ +{ + "summary": "The Voyager class handles AI communication, environment setup, agent creation, rollout configuration, action updates, messaging, result recording, exception management, and prints tasks completion/failure with ANSI coloring.", + "details": [ + { + "comment": "The code defines a class called \"Voyager\" that serves as the main module for an AI agent. It includes various attributes and functions related to the environment, agents (ActionAgent, CriticAgent, CurriculumAgent), SkillManager, and other settings such as server and model configurations. The class's purpose is to handle communication between the user, agent, and environment, and manage agent learning and decision-making processes.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/voyager.py\":0-34", + "content": "import copy\nimport json\nimport os\nimport time\nfrom typing import Dict\nimport voyager.utils as U\nfrom .env import VoyagerEnv\nfrom .agents import ActionAgent\nfrom .agents import CriticAgent\nfrom .agents import CurriculumAgent\nfrom .agents import SkillManager\n# TODO: remove event memory\nclass Voyager:\n def __init__(\n self,\n mc_port: int = None,\n azure_login: Dict[str, str] = None,\n server_port: int = 3000,\n openai_api_key: str = None,\n env_wait_ticks: int = 20,\n env_request_timeout: int = 600,\n max_iterations: int = 160,\n reset_placed_if_failed: bool = False,\n action_agent_model_name: str = \"gpt-4\",\n action_agent_temperature: float = 0,\n action_agent_task_max_retries: int = 4,\n action_agent_show_chat_log: bool = True,\n action_agent_show_execution_error: bool = True,\n curriculum_agent_model_name: str = \"gpt-4\",\n curriculum_agent_temperature: float = 0,\n curriculum_agent_qa_model_name: str = \"gpt-3.5-turbo\"," + }, + { + "comment": "The code defines a class for Voyager with various parameters. The action agent is for iterative prompting, the curriculum agent for automatic curriculum, critic agent for self-verification, and skill manager for skill library as per the paper.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/voyager.py\":35-56", + "content": " curriculum_agent_qa_temperature: float = 0,\n curriculum_agent_warm_up: Dict[str, int] = None,\n curriculum_agent_core_inventory_items: str = r\".*_log|.*_planks|stick|crafting_table|furnace\"\n r\"|cobblestone|dirt|coal|.*_pickaxe|.*_sword|.*_axe\",\n curriculum_agent_mode: str = \"auto\",\n critic_agent_model_name: str = \"gpt-4\",\n critic_agent_temperature: float = 0,\n critic_agent_mode: str = \"auto\",\n skill_manager_model_name: str = \"gpt-3.5-turbo\",\n skill_manager_temperature: float = 0,\n skill_manager_retrieval_top_k: int = 5,\n openai_api_request_timeout: int = 240,\n ckpt_dir: str = \"ckpt\",\n skill_library_dir: str = None,\n resume: bool = False,\n ):\n \"\"\"\n The main class for Voyager.\n Action agent is the iterative prompting mechanism in paper.\n Curriculum agent is the automatic curriculum in paper.\n Critic agent is the self-verification in paper.\n Skill manager is the skill library in paper." + }, + { + "comment": "This code defines function parameters for a Minecraft bot. Parameters include the minecraft port, Azure login credentials, Mineflayer port, OpenAI API key, wait time for each step, timeout for execution, reset option, and model names/temperatures for action and curriculum agents.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/voyager.py\":57-71", + "content": " :param mc_port: minecraft in-game port\n :param azure_login: minecraft login config\n :param server_port: mineflayer port\n :param openai_api_key: openai api key\n :param env_wait_ticks: how many ticks at the end each step will wait, if you found some chat log missing,\n you should increase this value\n :param env_request_timeout: how many seconds to wait for each step, if the code execution exceeds this time,\n python side will terminate the connection and need to be resumed\n :param reset_placed_if_failed: whether to reset placed blocks if failed, useful for building task\n :param action_agent_model_name: action agent model name\n :param action_agent_temperature: action agent temperature\n :param action_agent_task_max_retries: how many times to retry if failed\n :param curriculum_agent_model_name: curriculum agent model name\n :param curriculum_agent_temperature: curriculum agent temperature\n :param curriculum_agent_qa_model_name: curriculum agent qa model name" + }, + { + "comment": "This code is defining the parameters for creating a curriculum and setting up agents. The curriculum_agent has temperature, warm-up, and mode parameters while the critic_agent has its own temperature and mode. It also provides available keys for different game attributes like context, biome, time, etc. The \"reached in warm up\" statement suggests that some items will only appear later in the learning process.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/voyager.py\":72-93", + "content": " :param curriculum_agent_qa_temperature: curriculum agent qa temperature\n :param curriculum_agent_warm_up: info will show in curriculum human message\n if completed task larger than the value in dict, available keys are:\n {\n \"context\": int,\n \"biome\": int,\n \"time\": int,\n \"other_blocks\": int,\n \"nearby_entities\": int,\n \"health\": int,\n \"hunger\": int,\n \"position\": int,\n \"equipment\": int,\n \"chests\": int,\n \"optional_inventory_items\": int,\n }\n :param curriculum_agent_core_inventory_items: only show these items in inventory before optional_inventory_items\n reached in warm up\n :param curriculum_agent_mode: \"auto\" for automatic curriculum, \"manual\" for human curriculum\n :param critic_agent_model_name: critic agent model name\n :param critic_agent_temperature: critic agent temperature\n :param critic_agent_mode: \"auto\" for automatic critic ,\"manual\" for human critic" + }, + { + "comment": "This code initializes a VoyagerEnv environment, sets environment variables, and initializes an ActionAgent. The environment is initialized with given parameters like mc_port, azure_login, server_port, request_timeout, env_request_timeout, reset_placed_if_failed, and max_iterations. OpenAI API key is set using os.environ. An ActionAgent is initialized with model_name and temperature.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/voyager.py\":94-119", + "content": " :param skill_manager_model_name: skill manager model name\n :param skill_manager_temperature: skill manager temperature\n :param skill_manager_retrieval_top_k: how many skills to retrieve for each task\n :param openai_api_request_timeout: how many seconds to wait for openai api\n :param ckpt_dir: checkpoint dir\n :param skill_library_dir: skill library dir\n :param resume: whether to resume from checkpoint\n \"\"\"\n # init env\n self.env = VoyagerEnv(\n mc_port=mc_port,\n azure_login=azure_login,\n server_port=server_port,\n request_timeout=env_request_timeout,\n )\n self.env_wait_ticks = env_wait_ticks\n self.reset_placed_if_failed = reset_placed_if_failed\n self.max_iterations = max_iterations\n # set openai api key\n os.environ[\"OPENAI_API_KEY\"] = openai_api_key\n # init agents\n self.action_agent = ActionAgent(\n model_name=action_agent_model_name,\n temperature=action_agent_temperature," + }, + { + "comment": "The code initializes three agent instances: `action_agent`, `curriculum_agent`, and `critic_agent`. Each agent is initialized with specific parameters, such as model names, temperatures, request timeouts, checkpoint directories, modes, and warm-up values. These agents are likely used for different tasks within the AI system.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/voyager.py\":120-142", + "content": " request_timout=openai_api_request_timeout,\n ckpt_dir=ckpt_dir,\n resume=resume,\n chat_log=action_agent_show_chat_log,\n execution_error=action_agent_show_execution_error,\n )\n self.action_agent_task_max_retries = action_agent_task_max_retries\n self.curriculum_agent = CurriculumAgent(\n model_name=curriculum_agent_model_name,\n temperature=curriculum_agent_temperature,\n qa_model_name=curriculum_agent_qa_model_name,\n qa_temperature=curriculum_agent_qa_temperature,\n request_timout=openai_api_request_timeout,\n ckpt_dir=ckpt_dir,\n resume=resume,\n mode=curriculum_agent_mode,\n warm_up=curriculum_agent_warm_up,\n core_inventory_items=curriculum_agent_core_inventory_items,\n )\n self.critic_agent = CriticAgent(\n model_name=critic_agent_model_name,\n temperature=critic_agent_temperature,\n request_timout=openai_api_request_timeout," + }, + { + "comment": "This code initializes a Voyager agent with the specified critic agent mode and skill manager parameters. It also creates an event recorder and sets up variables for rollout. The reset function resets the agent's state with the given task and context, optionally resetting the environment as well.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/voyager.py\":143-171", + "content": " mode=critic_agent_mode,\n )\n self.skill_manager = SkillManager(\n model_name=skill_manager_model_name,\n temperature=skill_manager_temperature,\n retrieval_top_k=skill_manager_retrieval_top_k,\n request_timout=openai_api_request_timeout,\n ckpt_dir=skill_library_dir if skill_library_dir else ckpt_dir,\n resume=True if resume or skill_library_dir else False,\n )\n self.recorder = U.EventRecorder(ckpt_dir=ckpt_dir, resume=resume)\n self.resume = resume\n # init variables for rollout\n self.action_agent_rollout_num_iter = -1\n self.task = None\n self.context = \"\"\n self.messages = None\n self.conversations = []\n self.last_events = None\n def reset(self, task, context=\"\", reset_env=True):\n self.action_agent_rollout_num_iter = 0\n self.task = task\n self.context = context\n if reset_env:\n self.env.reset(\n options={\n \"mode\": \"soft\"," + }, + { + "comment": "This code is setting up the environment for an agent, defining difficulty level and retrieving skills. It then renders a system message and a human message using the ActionAgent. The messages are stored in self.messages and their length is checked to ensure there are two messages. A debug statement is also printed to display the human message content.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/voyager.py\":172-195", + "content": " \"wait_ticks\": self.env_wait_ticks,\n }\n )\n difficulty = (\n \"easy\" if len(self.curriculum_agent.completed_tasks) > 15 else \"peaceful\"\n )\n # step to peek an observation\n events = self.env.step(\n \"bot.chat(`/time set ${getNextTime()}`);\\n\"\n + f\"bot.chat('/difficulty {difficulty}');\"\n )\n skills = self.skill_manager.retrieve_skills(query=self.context)\n print(\n f\"\\033[33mRender Action Agent system message with {len(skills)} skills\\033[0m\"\n )\n system_message = self.action_agent.render_system_message(skills=skills)\n human_message = self.action_agent.render_human_message(\n events=events, code=\"\", task=self.task, context=context, critique=\"\"\n )\n self.messages = [system_message, human_message]\n print(\n f\"\\033[32m****Action Agent human message****\\n{human_message.content}\\033[0m\"\n )\n assert len(self.messages) == 2" + }, + { + "comment": "This code initializes an empty conversations list, returns the messages, closes the environment, steps through the agent's actions, and updates the critic agent's success check.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/voyager.py\":196-220", + "content": " self.conversations = []\n return self.messages\n def close(self):\n self.env.close()\n def step(self):\n if self.action_agent_rollout_num_iter < 0:\n raise ValueError(\"Agent must be reset before stepping\")\n ai_message = self.action_agent.llm(self.messages)\n print(f\"\\033[34m****Action Agent ai message****\\n{ai_message.content}\\033[0m\")\n self.conversations.append(\n (self.messages[0].content, self.messages[1].content, ai_message.content)\n )\n parsed_result = self.action_agent.process_ai_message(message=ai_message)\n success = False\n if isinstance(parsed_result, dict):\n code = parsed_result[\"program_code\"] + \"\\n\" + parsed_result[\"exec_code\"]\n events = self.env.step(\n code,\n programs=self.skill_manager.programs,\n )\n self.recorder.record(events, self.task)\n self.action_agent.update_chest_memory(events[-1][1][\"nearbyChests\"])\n success, critique = self.critic_agent.check_task_success(" + }, + { + "comment": "This code is creating an event object and calling a step function to execute it. If the execution fails, it reverts all placing events in the last step by using givePlacedItemBack function and updating inventory.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/voyager.py\":221-242", + "content": " events=events,\n task=self.task,\n context=self.context,\n chest_observation=self.action_agent.render_chest_observation(),\n max_retries=5,\n )\n if self.reset_placed_if_failed and not success:\n # revert all the placing event in the last step\n blocks = []\n positions = []\n for event_type, event in events:\n if event_type == \"onSave\" and event[\"onSave\"].endswith(\"_placed\"):\n block = event[\"onSave\"].split(\"_placed\")[0]\n position = event[\"status\"][\"position\"]\n blocks.append(block)\n positions.append(position)\n new_events = self.env.step(\n f\"await givePlacedItemBack(bot, {U.json_dumps(blocks)}, {U.json_dumps(positions)})\",\n programs=self.skill_manager.programs,\n )\n events[-1][1][\"inventory\"] = new_events[-1][1][\"inventory\"]" + }, + { + "comment": "The code updates the events list, retrieves new skills using the SkillManager, generates system and human messages, handles failed parsing attempts, records results, prints a message and increments rollout number if max retries are reached.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/voyager.py\":243-266", + "content": " events[-1][1][\"voxels\"] = new_events[-1][1][\"voxels\"]\n new_skills = self.skill_manager.retrieve_skills(\n query=self.context\n + \"\\n\\n\"\n + self.action_agent.summarize_chatlog(events)\n )\n system_message = self.action_agent.render_system_message(skills=new_skills)\n human_message = self.action_agent.render_human_message(\n events=events,\n code=parsed_result[\"program_code\"],\n task=self.task,\n context=self.context,\n critique=critique,\n )\n self.last_events = copy.deepcopy(events)\n self.messages = [system_message, human_message]\n else:\n assert isinstance(parsed_result, str)\n self.recorder.record([], self.task)\n print(f\"\\033[34m{parsed_result} Trying again!\\033[0m\")\n assert len(self.messages) == 2\n self.action_agent_rollout_num_iter += 1\n done = (\n self.action_agent_rollout_num_iter >= self.action_agent_task_max_retries" + }, + { + "comment": "The code snippet defines a class with methods for rollout and learn. The rollout method performs continuous steps until done, returning messages, reward, and information. If successful, it retrieves program_code and program_name from parsed result. Otherwise, it prints the last human message. The learn method resumes learning if self.resume is true.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/voyager.py\":267-295", + "content": " or success\n )\n info = {\n \"task\": self.task,\n \"success\": success,\n \"conversations\": self.conversations,\n }\n if success:\n assert (\n \"program_code\" in parsed_result and \"program_name\" in parsed_result\n ), \"program and program_name must be returned when success\"\n info[\"program_code\"] = parsed_result[\"program_code\"]\n info[\"program_name\"] = parsed_result[\"program_name\"]\n else:\n print(\n f\"\\033[32m****Action Agent human message****\\n{self.messages[-1].content}\\033[0m\"\n )\n return self.messages, 0, done, info\n def rollout(self, *, task, context, reset_env=True):\n self.reset(task=task, context=context, reset_env=reset_env)\n while True:\n messages, reward, done, info = self.step()\n if done:\n break\n return messages, reward, done, info\n def learn(self, reset_env=True):\n if self.resume:" + }, + { + "comment": "The code resets the environment and handles inventory management depending on a condition. It then checks if the iteration limit is reached, proposes the next task using curriculum agent, and prints the start of the task with maximum retries specified.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/voyager.py\":296-325", + "content": " # keep the inventory\n self.env.reset(\n options={\n \"mode\": \"soft\",\n \"wait_ticks\": self.env_wait_ticks,\n }\n )\n else:\n # clear the inventory\n self.env.reset(\n options={\n \"mode\": \"hard\",\n \"wait_ticks\": self.env_wait_ticks,\n }\n )\n self.resume = True\n self.last_events = self.env.step(\"\")\n while True:\n if self.recorder.iteration > self.max_iterations:\n print(\"Iteration limit reached\")\n break\n task, context = self.curriculum_agent.propose_next_task(\n events=self.last_events,\n chest_observation=self.action_agent.render_chest_observation(),\n max_retries=5,\n )\n print(\n f\"\\033[35mStarting task {task} for at most {self.action_agent_task_max_retries} times\\033[0m\"\n )" + }, + { + "comment": "Code attempts to perform a rollout task, but if an exception occurs, it sleeps for 3 seconds, resets the bot status with specific options, prints an error message, and returns information with \"success\" set to False.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/voyager.py\":326-349", + "content": " try:\n messages, reward, done, info = self.rollout(\n task=task,\n context=context,\n reset_env=reset_env,\n )\n except Exception as e:\n time.sleep(3) # wait for mineflayer to exit\n info = {\n \"task\": task,\n \"success\": False,\n }\n # reset bot status here\n self.last_events = self.env.reset(\n options={\n \"mode\": \"hard\",\n \"wait_ticks\": self.env_wait_ticks,\n \"inventory\": self.last_events[-1][1][\"inventory\"],\n \"equipment\": self.last_events[-1][1][\"status\"][\"equipment\"],\n \"position\": self.last_events[-1][1][\"status\"][\"position\"],\n }\n )\n # use red color background to print the error\n print(\"Your last round rollout terminated due to error:\")" + }, + { + "comment": "The code includes functions for handling tasks, adding new skills, updating exploration progress, and retrieving completed and failed tasks. It also handles the environment reset and inference.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/voyager.py\":350-379", + "content": " print(f\"\\033[41m{e}\\033[0m\")\n if info[\"success\"]:\n self.skill_manager.add_new_skill(info)\n self.curriculum_agent.update_exploration_progress(info)\n print(\n f\"\\033[35mCompleted tasks: {', '.join(self.curriculum_agent.completed_tasks)}\\033[0m\"\n )\n print(\n f\"\\033[35mFailed tasks: {', '.join(self.curriculum_agent.failed_tasks)}\\033[0m\"\n )\n return {\n \"completed_tasks\": self.curriculum_agent.completed_tasks,\n \"failed_tasks\": self.curriculum_agent.failed_tasks,\n \"skills\": self.skill_manager.skills,\n }\n def decompose_task(self, task):\n if not self.last_events:\n self.last_events = self.env.reset(\n options={\n \"mode\": \"hard\",\n \"wait_ticks\": self.env_wait_ticks,\n }\n )\n return self.curriculum_agent.decompose_task(task, self.last_events)\n def inference(self, task=None, sub_goals=[], reset_mode=\"hard\", reset_env=True):" + }, + { + "comment": "Checks if either task or sub_goals are provided, and resets the environment. Then iterates through sub_goals, prints current goal and runs rollout for each one, updating exploration progress along the way.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/voyager.py\":380-404", + "content": " if not task and not sub_goals:\n raise ValueError(\"Either task or sub_goals must be provided\")\n if not sub_goals:\n sub_goals = self.decompose_task(task)\n self.env.reset(\n options={\n \"mode\": reset_mode,\n \"wait_ticks\": self.env_wait_ticks,\n }\n )\n self.curriculum_agent.completed_tasks = []\n self.curriculum_agent.failed_tasks = []\n self.last_events = self.env.step(\"\")\n while self.curriculum_agent.progress < len(sub_goals):\n next_task = sub_goals[self.curriculum_agent.progress]\n context = self.curriculum_agent.get_task_context(next_task)\n print(\n f\"\\033[35mStarting task {next_task} for at most {self.action_agent_task_max_retries} times\\033[0m\"\n )\n messages, reward, done, info = self.rollout(\n task=next_task,\n context=context,\n reset_env=reset_env,\n )\n self.curriculum_agent.update_exploration_progress(info)" + }, + { + "comment": "This code is printing the completed and failed tasks in a formatted way using ANSI escape sequences for coloring.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/voyager.py\":405-410", + "content": " print(\n f\"\\033[35mCompleted tasks: {', '.join(self.curriculum_agent.completed_tasks)}\\033[0m\"\n )\n print(\n f\"\\033[35mFailed tasks: {', '.join(self.curriculum_agent.failed_tasks)}\\033[0m\"\n )" + } + ] +} \ No newline at end of file diff --git a/docs/doc/9ce89501-9d1d-4750-923b-164ff975bcec.json b/docs/doc/9ce89501-9d1d-4750-923b-164ff975bcec.json new file mode 100644 index 00000000..b43dd862 --- /dev/null +++ b/docs/doc/9ce89501-9d1d-4750-923b-164ff975bcec.json @@ -0,0 +1,25 @@ +{ + "summary": "This function enables bot-controlled chest interaction, allowing users to teleport, open/close chests, list items, emit events, and search by name.", + "details": [ + { + "comment": "Function to retrieve items from a chest at specified position using Vec3. Moves the bot to the chest location, opens it, and retrieves specific items from the container. Displays error messages if an item is not found or insufficient quantity exists in the chest. Closes the chest after completion.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/control_primitives/useChest.js\":0-30", + "content": "async function getItemFromChest(bot, chestPosition, itemsToGet) {\n // return if chestPosition is not Vec3\n if (!(chestPosition instanceof Vec3)) {\n bot.chat(\"chestPosition for getItemFromChest must be a Vec3\");\n return;\n }\n await moveToChest(bot, chestPosition);\n const chestBlock = bot.blockAt(chestPosition);\n const chest = await bot.openContainer(chestBlock);\n for (const name in itemsToGet) {\n const itemByName = mcData.itemsByName[name];\n if (!itemByName) {\n bot.chat(`No item named ${name}`);\n continue;\n }\n const item = chest.findContainerItem(itemByName.id);\n if (!item) {\n bot.chat(`I don't see ${name} in this chest`);\n continue;\n }\n try {\n await chest.withdraw(item.type, null, itemsToGet[name]);\n } catch (err) {\n bot.chat(`Not enough ${name} in chest.`);\n }\n }\n await closeChest(bot, chestBlock);\n}\nasync function depositItemIntoChest(bot, chestPosition, itemsToDeposit) {" + }, + { + "comment": "Code snippet checks if chestPosition is a Vec3, then moves to the chest position, opens the chest, iterates through itemsToDeposit, deposits each item into the chest if available, and finally closes the chest.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/control_primitives/useChest.js\":31-61", + "content": " // return if chestPosition is not Vec3\n if (!(chestPosition instanceof Vec3)) {\n throw new Error(\n \"chestPosition for depositItemIntoChest must be a Vec3\"\n );\n }\n await moveToChest(bot, chestPosition);\n const chestBlock = bot.blockAt(chestPosition);\n const chest = await bot.openContainer(chestBlock);\n for (const name in itemsToDeposit) {\n const itemByName = mcData.itemsByName[name];\n if (!itemByName) {\n bot.chat(`No item named ${name}`);\n continue;\n }\n const item = bot.inventory.findInventoryItem(itemByName.id);\n if (!item) {\n bot.chat(`No ${name} in inventory`);\n continue;\n }\n try {\n await chest.deposit(item.type, null, itemsToDeposit[name]);\n } catch (err) {\n bot.chat(`Not enough ${name} in inventory.`);\n }\n }\n await closeChest(bot, chestBlock);\n}\nasync function checkItemInsideChest(bot, chestPosition) {\n // return if chestPosition is not Vec3" + }, + { + "comment": "Code snippet handles depositing an item into a chest. First, it ensures the 'chestPosition' is a Vec3 instance and throws an error if not. It then moves the bot to the specified chest position using teleportation if it's more than 32 blocks away. After reaching the chest, it checks whether the block at that position is indeed a chest or not. If not, it removes the chest and throws an error with the current block name.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/control_primitives/useChest.js\":62-93", + "content": " if (!(chestPosition instanceof Vec3)) {\n throw new Error(\n \"chestPosition for depositItemIntoChest must be a Vec3\"\n );\n }\n await moveToChest(bot, chestPosition);\n const chestBlock = bot.blockAt(chestPosition);\n await bot.openContainer(chestBlock);\n await closeChest(bot, chestBlock);\n}\nasync function moveToChest(bot, chestPosition) {\n if (!(chestPosition instanceof Vec3)) {\n throw new Error(\n \"chestPosition for depositItemIntoChest must be a Vec3\"\n );\n }\n if (chestPosition.distanceTo(bot.entity.position) > 32) {\n bot.chat(\n `/tp ${chestPosition.x} ${chestPosition.y} ${chestPosition.z}`\n );\n await bot.waitForTicks(20);\n }\n const chestBlock = bot.blockAt(chestPosition);\n if (chestBlock.name !== \"chest\") {\n bot.emit(\"removeChest\", chestPosition);\n throw new Error(\n `No chest at ${chestPosition}, it is ${chestBlock.name}`\n );\n }\n await bot.pathfinder.goto(\n new GoalLookAtBlock(chestBlock.position, bot.world, {})" + }, + { + "comment": "This code defines functions for interacting with chests in a game. It opens the chest, lists items inside and emits corresponding events, closes the chest after interaction, and provides an itemByName function to find an item by name from a list of items.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/control_primitives/useChest.js\":94-132", + "content": " );\n return chestBlock;\n}\nasync function listItemsInChest(bot, chestBlock) {\n const chest = await bot.openContainer(chestBlock);\n const items = chest.containerItems();\n if (items.length > 0) {\n const itemNames = items.reduce((acc, obj) => {\n if (acc[obj.name]) {\n acc[obj.name] += obj.count;\n } else {\n acc[obj.name] = obj.count;\n }\n return acc;\n }, {});\n bot.emit(\"closeChest\", itemNames, chestBlock.position);\n } else {\n bot.emit(\"closeChest\", {}, chestBlock.position);\n }\n return chest;\n}\nasync function closeChest(bot, chestBlock) {\n try {\n const chest = await listItemsInChest(bot, chestBlock);\n await chest.close();\n } catch (err) {\n await bot.closeWindow(chestBlock);\n }\n}\nfunction itemByName(items, name) {\n for (let i = 0; i < items.length; ++i) {\n const item = items[i];\n if (item && item.name === name) return item;\n }\n return null;\n}" + } + ] +} \ No newline at end of file diff --git a/docs/doc/a043843a-3b20-4b10-9c1a-588be1823c33.json b/docs/doc/a043843a-3b20-4b10-9c1a-588be1823c33.json new file mode 100644 index 00000000..d2fcb339 --- /dev/null +++ b/docs/doc/a043843a-3b20-4b10-9c1a-588be1823c33.json @@ -0,0 +1,40 @@ +{ + "summary": "Voyager AI agent explores Minecraft, learns autonomously, outperforms SOTA, generalizes tasks, MIT licensed, expected 2023 Arxiv preprint (Arxiv-2305.16291).", + "details": [ + { + "comment": "This code introduces Voyager, an embodied agent powered by large language models that continuously explores Minecraft, acquires skills, and discovers new things without human intervention. It consists of three main components: an automatic curriculum for exploration, a language model, and an interactive environment in Minecraft.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/README.md\":0-23", + "content": "# Voyager: An Open-Ended Embodied Agent with Large Language Models\n
\n[[Website]](https://voyager.minedojo.org/)\n[[Arxiv]](https://arxiv.org/abs/2305.16291)\n[[PDF]](https://voyager.minedojo.org/assets/documents/voyager.pdf)\n[[Tweet]](https://twitter.com/DrJimFan/status/1662115266933972993?s=20)\n[![Python Version](https://img.shields.io/badge/Python-3.9-blue.svg)](https://github.com/MineDojo/Voyager)\n[![GitHub license](https://img.shields.io/github/license/MineDojo/Voyager)](https://github.com/MineDojo/Voyager/blob/main/LICENSE)\n______________________________________________________________________\nhttps://github.com/MineDojo/Voyager/assets/25460983/ce29f45b-43a5-4399-8fd8-5dd105fd64f2\n![](images/pull.png)\n
\nWe introduce Voyager, the first LLM-powered embodied lifelong learning agent\nin Minecraft that continuously explores the world, acquires diverse skills, and\nmakes novel discoveries without human intervention. Voyager consists of three\nkey components: 1) an automatic curriculum that maximizes exploration, 2) an" + }, + { + "comment": "This code snippet describes Voyager's functionality: a skill library for storing and retrieving complex behaviors, an iterative prompting mechanism incorporating feedback, and interaction with GPT-4 through blackbox queries. It shows strong in-context lifelong learning capabilities, outperforming prior SOTA in Minecraft, generalizing to novel tasks from scratch. The codebase is under MIT License.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/README.md\":24-37", + "content": "ever-growing skill library of executable code for storing and retrieving complex\nbehaviors, and 3) a new iterative prompting mechanism that incorporates environment\nfeedback, execution errors, and self-verification for program improvement.\nVoyager interacts with GPT-4 via blackbox queries, which bypasses the need for\nmodel parameter fine-tuning. The skills developed by Voyager are temporally\nextended, interpretable, and compositional, which compounds the agent\u2019s abilities\nrapidly and alleviates catastrophic forgetting. Empirically, Voyager shows\nstrong in-context lifelong learning capability and exhibits exceptional proficiency\nin playing Minecraft. It obtains 3.3\u00d7 more unique items, travels 2.3\u00d7 longer\ndistances, and unlocks key tech tree milestones up to 15.3\u00d7 faster than prior SOTA.\nVoyager is able to utilize the learned skill library in a new Minecraft world to\nsolve novel tasks from scratch, while other techniques struggle to generalize.\nIn this repo, we provide Voyager code. This codebase is under [MIT License](LICENSE)." + }, + { + "comment": "This code provides instructions for installing Voyager, a tool that requires Python \u2265 3.9 and Node.js \u2265 16.13.0. It outlines steps for Python, Node.js, Minecraft instance, and Fabric mods installation with links to additional tutorials.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/README.md\":39-71", + "content": "# Installation\nVoyager requires Python \u2265 3.9 and Node.js \u2265 16.13.0. We have tested on Ubuntu 20.04, Windows 11, and macOS. You need to follow the instructions below to install Voyager.\n## Python Install\n```\ngit clone https://github.com/MineDojo/Voyager\ncd Voyager\npip install -e .\n```\n## Node.js Install\nIn addition to the Python dependencies, you need to install the following Node.js packages:\n```\ncd voyager/env/mineflayer\nnpm install -g npx\nnpm install\ncd mineflayer-collectblock\nnpx tsc\ncd ..\nnpm install\n```\n## Minecraft Instance Install\nVoyager depends on Minecraft game. You need to install Minecraft game and set up a Minecraft instance.\nFollow the instructions in [Minecraft Login Tutorial](installation/minecraft_instance_install.md) to set up your Minecraft Instance.\n## Fabric Mods Install\nYou need to install fabric mods to support all the features in Voyager. Remember to use the correct Fabric version of all the mods. \nFollow the instructions in [Fabric Mods Install](installation/fabric_mods_install.md) to install the mods." + }, + { + "comment": "To use Voyager, ensure you have an OpenAI API key and install the required dependencies. Create an azure_login dictionary with your client ID, redirect URL, secret value (optional), and version. Set openai_api_key to YOUR_API_KEY. Instantiate Voyager with these settings. First-time Azure Login users will generate a config file following instructions. For Azure Login, open the world and LAN, then run learn(). The game will start soon.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/README.md\":73-99", + "content": "# Getting Started\nVoyager uses OpenAI's GPT-4 as the language model. You need to have an OpenAI API key to use Voyager. You can get one from [here](https://platform.openai.com/account/api-keys).\nAfter the installation process, you can run Voyager by:\n```python\nfrom voyager import Voyager\n# You can also use mc_port instead of azure_login, but azure_login is highly recommended\nazure_login = {\n \"client_id\": \"YOUR_CLIENT_ID\",\n \"redirect_url\": \"https://127.0.0.1/auth-response\",\n \"secret_value\": \"[OPTIONAL] YOUR_SECRET_VALUE\",\n \"version\": \"fabric-loader-0.14.18-1.19\", # the version Voyager is tested on\n}\nopenai_api_key = \"YOUR_API_KEY\"\nvoyager = Voyager(\n azure_login=azure_login,\n openai_api_key=openai_api_key,\n)\n# start lifelong learning\nvoyager.learn()\n```\n* If you are running with `Azure Login` for the first time, it will ask you to follow the command line instruction to generate a config file.\n* For `Azure Login`, you also need to select the world and open the world to LAN by yourself. After you run `voyager.learn()` the game will pop up soon, you need to:" + }, + { + "comment": "1. The code provides instructions to set up a Minecraft world for Voyager learning.\n2. Users can resume from a checkpoint or run Voyager with a specific skill library.\n3. Required credentials, directories and settings need to be provided when instantiating the Voyager class.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/README.md\":100-129", + "content": " 1. Select `Singleplayer` and press `Create New World`.\n 2. Set Game Mode to `Creative` and Difficulty to `Peaceful`.\n 3. After the world is created, press `Esc` key and press `Open to LAN`.\n 4. Select `Allow cheats: ON` and press `Start LAN World`. You will see the bot join the world soon. \n# Resume from a checkpoint during learning\nIf you stop the learning process and want to resume from a checkpoint later, you can instantiate Voyager by:\n```python\nfrom voyager import Voyager\nvoyager = Voyager(\n azure_login=azure_login,\n openai_api_key=openai_api_key,\n ckpt_dir=\"YOUR_CKPT_DIR\",\n resume=True,\n)\n```\n# Run Voyager for a specific task with a learned skill library\nIf you want to run Voyager for a specific task with a learned skill library, you should first pass the skill library directory to Voyager:\n```python\nfrom voyager import Voyager\n# First instantiate Voyager with skill_library_dir.\nvoyager = Voyager(\n azure_login=azure_login,\n openai_api_key=openai_api_key,\n skill_library_dir=\"./skill_library/trial1\", # Load a learned skill library." + }, + { + "comment": "This code sets up the Voyager agent with a specified checkpoint directory and disables resuming from a skill library because this task is not learning-based. After setting up, it decomposes a given task into sub-goals using Voyager's task decomposition functionality. Finally, it runs inference on these sub-goals using the learned skill library. Valid skill libraries can be found at [Learned Skill Libraries](skill_library/README.md).", + "location": "\"/media/root/Prima/works/Voyager/docs/src/README.md\":130-156", + "content": " ckpt_dir=\"YOUR_CKPT_DIR\", # Feel free to use a new dir. Do not use the same dir as skill library because new events will still be recorded to ckpt_dir. \n resume=False, # Do not resume from a skill library because this is not learning.\n)\n```\nThen, you can run task decomposition. Notice: Occasionally, the task decomposition may not be logical. If you notice the printed sub-goals are flawed, you can rerun the decomposition.\n```python\n# Run task decomposition\ntask = \"YOUR TASK\" # e.g. \"Craft a diamond pickaxe\"\nsub_goals = voyager.decompose_task(task=task)\n```\nFinally, you can run the sub-goals with the learned skill library:\n```python\nvoyager.inference(sub_goals=sub_goals)\n```\nFor all valid skill libraries, see [Learned Skill Libraries](skill_library/README.md).\n# FAQ\nIf you have any questions, please check our [FAQ](FAQ.md) first before opening an issue.\n# Paper and Citation\nIf you find our work useful, please consider citing us! \n```bibtex\n@article{wang2023voyager,\n title = {Voyager: An Open-Ended Embodied Agent with Large Language Models}," + }, + { + "comment": "The code block represents the author, year, and journal information for a research paper. The paper is expected to be published in 2023 as an arXiv preprint with identifier Arxiv-2305.16291.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/README.md\":157-163", + "content": " author = {Guanzhi Wang and Yuqi Xie and Yunfan Jiang and Ajay Mandlekar and Chaowei Xiao and Yuke Zhu and Linxi Fan and Anima Anandkumar},\n year = {2023},\n journal = {arXiv preprint arXiv: Arxiv-2305.16291}\n}\n```\nDisclaimer: This project is strictly for research purposes, and not an official product from NVIDIA." + } + ] +} \ No newline at end of file diff --git a/docs/doc/a4466f2a-cef9-4fe2-b7a1-f0ffd42b90cd.json b/docs/doc/a4466f2a-cef9-4fe2-b7a1-f0ffd42b90cd.json new file mode 100644 index 00000000..f903d8ca --- /dev/null +++ b/docs/doc/a4466f2a-cef9-4fe2-b7a1-f0ffd42b90cd.json @@ -0,0 +1,15 @@ +{ + "summary": "This code sets up a Python package using setup.py, defining essential properties like name, requirements, and extras while adhering to given classifiers for development status, topic, and language.", + "details": [ + { + "comment": "This code is setting up the Voyager Python package. It imports necessary modules, defines constants like PKG_NAME and VERSION, reads the installation requirements from a \"requirements.txt\" file, fills in extras if needed, and finally sets up the package using setup() function with various properties.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/setup.py\":0-41", + "content": "import os\nimport pathlib\nimport pkg_resources\nfrom setuptools import setup, find_packages\nPKG_NAME = \"voyager\"\nVERSION = \"0.1\"\nEXTRAS = {}\ndef _read_file(fname):\n # this_dir = os.path.abspath(os.path.dirname(__file__))\n # with open(os.path.join(this_dir, fname)) as f:\n with pathlib.Path(fname).open(encoding=\"utf-8\") as fp:\n return fp.read()\ndef _read_install_requires():\n with pathlib.Path(\"requirements.txt\").open() as fp:\n return [\n str(requirement) for requirement in pkg_resources.parse_requirements(fp)\n ]\ndef _fill_extras(extras):\n if extras:\n extras[\"all\"] = list(set([item for group in extras.values() for item in group]))\n return extras\nsetup(\n name=PKG_NAME,\n version=VERSION,\n author=f\"MineDojo Team\",\n url=\"https://github.com/MineDojo/Voyager\",\n description=\"research project\",\n long_description=_read_file(\"README.md\"),\n long_description_content_type=\"text/markdown\",\n keywords=[\n \"Open-Ended Learning\",\n \"Lifelong Learning\"," + }, + { + "comment": "This code is for setting up a Python package using setup.py. It includes the package name, author, description, license, packages to include, installation requirements, and extras. The classifiers specify development status, topic, and programming language.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/setup.py\":42-58", + "content": " \"Embodied Agents\",\n \"Large Language Models\",\n ],\n license=\"MIT License\",\n packages=find_packages(include=f\"{PKG_NAME}.*\"),\n include_package_data=True,\n zip_safe=False,\n install_requires=_read_install_requires(),\n extras_require=_fill_extras(EXTRAS),\n python_requires=\">=3.9\",\n classifiers=[\n \"Development Status :: 5 - Production/Stable\",\n \"Topic :: Scientific/Engineering :: Artificial Intelligence\",\n \"Environment :: Console\",\n \"Programming Language :: Python :: 3.9\",\n ],\n)" + } + ] +} \ No newline at end of file diff --git a/docs/doc/a48a6ce2-4afb-43c4-b844-859316607398.json b/docs/doc/a48a6ce2-4afb-43c4-b844-859316607398.json new file mode 100644 index 00000000..d833e977 --- /dev/null +++ b/docs/doc/a48a6ce2-4afb-43c4-b844-859316607398.json @@ -0,0 +1,30 @@ +{ + "summary": "The code initializes a SkillManager class, sets up language model (ChatOpenAI), creates directories for skill storage, loads control primitives, handles checkpoint resuming, manages skills, and utilizes VectorDB for data persistence with conflict resolution. The method retrieves top K skills based on query using vector database similarity search and returns their codes.", + "details": [ + { + "comment": "The code initializes a SkillManager class, sets up the language model (ChatOpenAI), creates directories for skill storage, loads control primitives, and handles resuming from previous checkpoints if necessary.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/agents/skill.py\":0-33", + "content": "import os\nimport voyager.utils as U\nfrom langchain.chat_models import ChatOpenAI\nfrom langchain.embeddings.openai import OpenAIEmbeddings\nfrom langchain.schema import HumanMessage, SystemMessage\nfrom langchain.vectorstores import Chroma\nfrom voyager.prompts import load_prompt\nfrom voyager.control_primitives import load_control_primitives\nclass SkillManager:\n def __init__(\n self,\n model_name=\"gpt-3.5-turbo\",\n temperature=0,\n retrieval_top_k=5,\n request_timout=120,\n ckpt_dir=\"ckpt\",\n resume=False,\n ):\n self.llm = ChatOpenAI(\n model_name=model_name,\n temperature=temperature,\n request_timeout=request_timout,\n )\n U.f_mkdir(f\"{ckpt_dir}/skill/code\")\n U.f_mkdir(f\"{ckpt_dir}/skill/description\")\n U.f_mkdir(f\"{ckpt_dir}/skill/vectordb\")\n # programs for env execution\n self.control_primitives = load_control_primitives()\n if resume:\n print(f\"\\033[33mLoading Skill Manager from {ckpt_dir}/skill\\033[0m\")" + }, + { + "comment": "The code initializes a SkillManager instance with skills loaded from a JSON file or an empty dictionary if none exists, sets retrieval_top_k and ckpt_dir attributes, creates a Chroma VectorDB with OpenAIEmbeddings, and asserts that the number of skills in the VectorDB matches the length of the skills.json file. It also provides a program property that returns programs from the skills.json file and control primitives.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/agents/skill.py\":34-56", + "content": " self.skills = U.load_json(f\"{ckpt_dir}/skill/skills.json\")\n else:\n self.skills = {}\n self.retrieval_top_k = retrieval_top_k\n self.ckpt_dir = ckpt_dir\n self.vectordb = Chroma(\n collection_name=\"skill_vectordb\",\n embedding_function=OpenAIEmbeddings(),\n persist_directory=f\"{ckpt_dir}/skill/vectordb\",\n )\n assert self.vectordb._collection.count() == len(self.skills), (\n f\"Skill Manager's vectordb is not synced with skills.json.\\n\"\n f\"There are {self.vectordb._collection.count()} skills in vectordb but {len(self.skills)} skills in skills.json.\\n\"\n f\"Did you set resume=False when initializing the manager?\\n\"\n f\"You may need to manually delete the vectordb directory for running from scratch.\"\n )\n @property\n def programs(self):\n programs = \"\"\n for skill_name, entry in self.skills.items():\n programs += f\"{entry['code']}\\n\\n\"\n for primitives in self.control_primitives:" + }, + { + "comment": "The code defines two functions: \"get_programs\" and \"add_new_skill\". The get_programs function generates a list of skills based on primitives, appends them to the programs variable with newline separators, and returns the final list. The add_new_skill function checks if a skill with the given name and code already exists. If it does, it deletes the existing skill and renames the current one to avoid conflicts before adding it to the database.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/agents/skill.py\":57-79", + "content": " programs += f\"{primitives}\\n\\n\"\n return programs\n def add_new_skill(self, info):\n if info[\"task\"].startswith(\"Deposit useless items into the chest at\"):\n # No need to reuse the deposit skill\n return\n program_name = info[\"program_name\"]\n program_code = info[\"program_code\"]\n skill_description = self.generate_skill_description(program_name, program_code)\n print(\n f\"\\033[33mSkill Manager generated description for {program_name}:\\n{skill_description}\\033[0m\"\n )\n if program_name in self.skills:\n print(f\"\\033[33mSkill {program_name} already exists. Rewriting!\\033[0m\")\n self.vectordb._collection.delete(ids=[program_name])\n i = 2\n while f\"{program_name}V{i}.js\" in os.listdir(f\"{self.ckpt_dir}/skill/code\"):\n i += 1\n dumped_program_name = f\"{program_name}V{i}\"\n else:\n dumped_program_name = program_name\n self.vectordb.add_texts(" + }, + { + "comment": "This code is responsible for managing skills in a program, creating the necessary files and folders to store skill information such as code, description, and metadata. It uses VectorDB to persist data and U.dump functions to write text and JSON files. The `generate_skill_description` function generates a description based on the provided program name and code by passing it through the LLM with a system message loaded from \"load_prompt('skill')\". This function is used in skill creation or editing scenarios.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/agents/skill.py\":80-107", + "content": " texts=[skill_description],\n ids=[program_name],\n metadatas=[{\"name\": program_name}],\n )\n self.skills[program_name] = {\n \"code\": program_code,\n \"description\": skill_description,\n }\n assert self.vectordb._collection.count() == len(\n self.skills\n ), \"vectordb is not synced with skills.json\"\n U.dump_text(\n program_code, f\"{self.ckpt_dir}/skill/code/{dumped_program_name}.js\"\n )\n U.dump_text(\n skill_description,\n f\"{self.ckpt_dir}/skill/description/{dumped_program_name}.txt\",\n )\n U.dump_json(self.skills, f\"{self.ckpt_dir}/skill/skills.json\")\n self.vectordb.persist()\n def generate_skill_description(self, program_name, program_code):\n messages = [\n SystemMessage(content=load_prompt(\"skill\")),\n HumanMessage(\n content=program_code\n + \"\\n\\n\"\n + f\"The main function is `{program_name}`.\"" + }, + { + "comment": "This code snippet defines a method in the Voyager/voyager/agents/skill.py file responsible for retrieving skills based on query. It uses vector database similarity search to find top K skills and returns their corresponding codes. The process involves printing status messages during skill retrieval, with potential error handling for cases where no results are found or the retrieved skills' names are not present in the skills dictionary.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/agents/skill.py\":108-126", + "content": " ),\n ]\n skill_description = f\" // { self.llm(messages).content}\"\n return f\"async function {program_name}(bot) {{\\n{skill_description}\\n}}\"\n def retrieve_skills(self, query):\n k = min(self.vectordb._collection.count(), self.retrieval_top_k)\n if k == 0:\n return []\n print(f\"\\033[33mSkill Manager retrieving for {k} skills\\033[0m\")\n docs_and_scores = self.vectordb.similarity_search_with_score(query, k=k)\n print(\n f\"\\033[33mSkill Manager retrieved skills: \"\n f\"{', '.join([doc.metadata['name'] for doc, _ in docs_and_scores])}\\033[0m\"\n )\n skills = []\n for doc, _ in docs_and_scores:\n skills.append(self.skills[doc.metadata[\"name\"]][\"code\"])\n return skills" + } + ] +} \ No newline at end of file diff --git a/docs/doc/a4bdada6-ff75-466a-9371-45bfb5a5c951.json b/docs/doc/a4bdada6-ff75-466a-9371-45bfb5a5c951.json new file mode 100644 index 00000000..15494d4b --- /dev/null +++ b/docs/doc/a4bdada6-ff75-466a-9371-45bfb5a5c951.json @@ -0,0 +1,10 @@ +{ + "summary": "This function, load_control_primitives, imports necessary packages and defines the path to the \"voyager\" package. If no primitive names are provided, it retrieves them from the \"control_primitives\" directory within the package. It then loads these primitives as text files into a list and returns them.", + "details": [ + { + "comment": "This function, load_control_primitives, imports necessary packages and defines the path to the \"voyager\" package. If no primitive names are provided, it retrieves them from the \"control_primitives\" directory within the package. It then loads these primitives as text files into a list and returns them.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/control_primitives/__init__.py\":0-17", + "content": "import pkg_resources\nimport os\nimport voyager.utils as U\ndef load_control_primitives(primitive_names=None):\n package_path = pkg_resources.resource_filename(\"voyager\", \"\")\n if primitive_names is None:\n primitive_names = [\n primitives[:-3]\n for primitives in os.listdir(f\"{package_path}/control_primitives\")\n if primitives.endswith(\".js\")\n ]\n primitives = [\n U.load_text(f\"{package_path}/control_primitives/{primitive_name}.js\")\n for primitive_name in primitive_names\n ]\n return primitives" + } + ] +} \ No newline at end of file diff --git a/docs/doc/a6a479f3-edb0-40b3-8c02-d20adba27433.json b/docs/doc/a6a479f3-edb0-40b3-8c02-d20adba27433.json new file mode 100644 index 00000000..a78be122 --- /dev/null +++ b/docs/doc/a6a479f3-edb0-40b3-8c02-d20adba27433.json @@ -0,0 +1,20 @@ +{ + "summary": "Summary: This code provides instructions for downloading Fabric Installer and JRE v17, installing mods, setting up respawn settings, and using fabric-loader for Minecraft gameplay.", + "details": [ + { + "comment": "Installing Fabric mods: Download Fabric Installer, select 1.19 version, place mods in mods folder, use fabric-loader-0.14.18-1.19 for gameplay.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/installation/fabric_mods_install.md\":0-5", + "content": "# Fabric Mods Install\nIn this tutorial, we will install the Fabric launcher and 5 mods. Remember to use the correct Fabric version that matches your game version (1.19) of all the mods. \n1. You can download the latest Fabric Installer from [here](https://fabricmc.net/use/installer/). For Windows users, just download the `.exe` file. For Mac or Ubuntu users, download the jar file and call `java -jar fabric-installer-0.11.2.jar` to install. Select game version to be `1.19` and loader version to be `0.14.18`. It will automatically detect your Minecraft game install location.\n2. After installing Fabric, you will have a `YOUR_MINECRAFT_GAME_LOCATION/mods` folder. You need to put all the mods under this folder. Also, you will have a `YOUR_MINECRAFT_GAME_LOCATION/versions/fabric-loader-0.14.18-1.19`. This is the version you will run the game with. \n3. Here are 4 mods that can be directly downloaded to `YOUR_MINECRAFT_GAME_LOCATION/mods` folder: \n * [Fabric API](https://modrinth.com/mod/fabric-api/version/0.58.0+1.19): Basic Fabric APIs." + }, + { + "comment": "The code provides a list of mods for Minecraft Fabric, including their descriptions and download links. It also outlines instructions for compiling the 'Better Respawn' mod manually, specifying version-specific changes to settings and build requirements.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/installation/fabric_mods_install.md\":6-12", + "content": " * [Mod Menu](https://cdn.modrinth.com/data/mOgUt4GM/versions/4.0.4/modmenu-4.0.4.jar): Used to manage all the mods that you download.\n * [Complete Config](https://www.curseforge.com/minecraft/mc-mods/completeconfig/download/3821056): Dependency of server pause.\n * [Multi Server Pause](https://www.curseforge.com/minecraft/mc-mods/multiplayer-server-pause-fabric/download/3822586): Used to pause the server when waiting for GPT-4 to reply.\n4. For the last mod [Better Respawn](https://github.com/xieleo5/better-respawn/tree/1.19), you need to manually clone and compile.\n * After you clone the repo, remove the `'forge'` string in the last line of `settings.gradle`. Then run `gradlew build` to compile the mod. You will find the compiled jar file in `better-respawn/fabric/build/libs/better-respawn-fabric-1.19-2.0.0.jar`. Put the jar file to the mod folder.\n * You will need a Java Runtime Environment v17+ to build `better-respawn`. Some newer JRE versions will error during build. Find th" + }, + { + "comment": "Download JRE v17 archive, modify properties file with respawn settings, change `azure_login` version to `fabric-loader-0.14.18-1.19`, and return to README for getting started.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/installation/fabric_mods_install.md\":12-21", + "content": "e JRE v17 archive [here](https://www.oracle.com/java/technologies/javase/jdk17-archive-downloads.html).\n * After you launch the game, go to `YOUR_MINECRAFT_GAME_LOCATION/config/better-respawn`, and modify the properties file with:\n ```\n respawn_block_range=32\n max_respawn_distance=32\n min_respawn_distance=0\n ```\n5. Don't forget to change the `version` in `azure_login` to `fabric-loader-0.14.18-1.19` that you are using. You can find it under `YOUR_MINECRAFT_GAME_LOCATION/version` folder.\nYou can return to [README.md](../README.md#getting-started) and getting started now." + } + ] +} \ No newline at end of file diff --git a/docs/doc/b5517dfb-e155-4cff-bb60-d95cfa18bf5e.json b/docs/doc/b5517dfb-e155-4cff-bb60-d95cfa18bf5e.json new file mode 100644 index 00000000..2fdce23a --- /dev/null +++ b/docs/doc/b5517dfb-e155-4cff-bb60-d95cfa18bf5e.json @@ -0,0 +1,15 @@ +{ + "summary": "This function verifies valid inputs and collects blocks using the bot's `collectBlock` method. It initializes a mine block operation, ignores path, specifies count, saves bot state as `${name}_mined`, and notifies user if errors occur or no blocks are found.", + "details": [ + { + "comment": "This function checks if the provided name and count are valid string and number respectively. It then searches for blocks with the specified name, and if found, collects them using the bot's `collectBlock` method. If no blocks are found or an error occurs during collection, it notifies the user accordingly.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/control_primitives/mineBlock.js\":0-31", + "content": "async function mineBlock(bot, name, count = 1) {\n // return if name is not string\n if (typeof name !== \"string\") {\n throw new Error(`name for mineBlock must be a string`);\n }\n if (typeof count !== \"number\") {\n throw new Error(`count for mineBlock must be a number`);\n }\n const blockByName = mcData.blocksByName[name];\n if (!blockByName) {\n throw new Error(`No block named ${name}`);\n }\n const blocks = bot.findBlocks({\n matching: [blockByName.id],\n maxDistance: 32,\n count: 1024,\n });\n if (blocks.length === 0) {\n bot.chat(`No ${name} nearby, please explore first`);\n _mineBlockFailCount++;\n if (_mineBlockFailCount > 10) {\n throw new Error(\n \"mineBlock failed too many times, make sure you explore before calling mineBlock\"\n );\n }\n return;\n }\n const targets = [];\n for (let i = 0; i < blocks.length; i++) {\n targets.push(bot.blockAt(blocks[i]));\n }\n await bot.collectBlock.collect(targets, {" + }, + { + "comment": "The code snippet initializes a mine block operation, ignoring any absence of path and specifying the count. After completion, it saves the bot's state as `${name}_mined`.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/control_primitives/mineBlock.js\":32-36", + "content": " ignoreNoPath: true,\n count: count,\n });\n bot.save(`${name}_mined`);\n}" + } + ] +} \ No newline at end of file diff --git a/docs/doc/bcdc101d-c140-48de-9dbd-3ab791e8dadd.json b/docs/doc/bcdc101d-c140-48de-9dbd-3ab791e8dadd.json new file mode 100644 index 00000000..9f873638 --- /dev/null +++ b/docs/doc/bcdc101d-c140-48de-9dbd-3ab791e8dadd.json @@ -0,0 +1,35 @@ +{ + "summary": "The `VoyagerEnv` class sets up a Minecraft server using Mineflayer, providing pause/unpause functionality and returning the mineflayer instance with Azure login. It also has methods to reset and render, and classes for stopping, closing, and rendering the server.", + "details": [ + { + "comment": "This code defines a class `VoyagerEnv` that inherits from `gym.Env`, which represents an environment in the gymnasium framework. The constructor takes parameters such as `mc_port`, `azure_login`, `server_host`, `server_port`, `request_timeout`, and `log_path`. If neither `mc_port` nor `azure_login` is specified, a ValueError will be raised. If both are specified, a warning is logged and `mc_port` is ignored. The class likely interacts with Minecraft instances using the `MinecraftInstance` and `SubprocessMonitor` classes.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/env/bridge.py\":0-37", + "content": "import os.path\nimport time\nimport warnings\nfrom typing import SupportsFloat, Any, Tuple, Dict\nimport requests\nimport json\nimport gymnasium as gym\nfrom gymnasium.core import ObsType\nimport voyager.utils as U\nfrom .minecraft_launcher import MinecraftInstance\nfrom .process_monitor import SubprocessMonitor\nclass VoyagerEnv(gym.Env):\n def __init__(\n self,\n mc_port=None,\n azure_login=None,\n server_host=\"http://127.0.0.1\",\n server_port=3000,\n request_timeout=600,\n log_path=\"./logs\",\n ):\n if not mc_port and not azure_login:\n raise ValueError(\"Either mc_port or azure_login must be specified\")\n if mc_port and azure_login:\n warnings.warn(\n \"Both mc_port and mc_login are specified, mc_port will be ignored\"\n )\n self.mc_port = mc_port\n self.azure_login = azure_login\n self.server = f\"{server_host}:{server_port}\"\n self.server_port = server_port\n self.request_timeout = request_timeout" + }, + { + "comment": "This code sets up a Minecraft server using Mineflayer and MinecraftInstance. It initializes the necessary variables, gets the mineflayer process running with specified server port, creates directories for logs, and returns the mineflayer instance if Azure login is provided, or None otherwise.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/env/bridge.py\":38-67", + "content": " self.log_path = log_path\n self.mineflayer = self.get_mineflayer_process(server_port)\n if azure_login:\n self.mc_instance = self.get_mc_instance()\n else:\n self.mc_instance = None\n self.has_reset = False\n self.reset_options = None\n self.connected = False\n self.server_paused = False\n def get_mineflayer_process(self, server_port):\n U.f_mkdir(self.log_path, \"mineflayer\")\n file_path = os.path.abspath(os.path.dirname(__file__))\n return SubprocessMonitor(\n commands=[\n \"node\",\n U.f_join(file_path, \"mineflayer/index.js\"),\n str(server_port),\n ],\n name=\"mineflayer\",\n ready_match=r\"Server started on port (\\d+)\",\n log_path=U.f_join(self.log_path, \"mineflayer\"),\n )\n def get_mc_instance(self):\n print(\"Creating Minecraft server\")\n U.f_mkdir(self.log_path, \"minecraft\")\n return MinecraftInstance(\n **self.azure_login," + }, + { + "comment": "The code checks if the Minecraft server and Mineflayer processes are running. If not, it starts the Minecraft server and restarts the Mineflayer process if it exits. It also makes a POST request.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/env/bridge.py\":68-92", + "content": " mineflayer=self.mineflayer,\n log_path=U.f_join(self.log_path, \"minecraft\"),\n )\n def check_process(self):\n if self.mc_instance and not self.mc_instance.is_running:\n # if self.mc_instance:\n # self.mc_instance.check_process()\n # if not self.mc_instance.is_running:\n print(\"Starting Minecraft server\")\n self.mc_instance.run()\n self.mc_port = self.mc_instance.port\n self.reset_options[\"port\"] = self.mc_instance.port\n print(f\"Server started on port {self.reset_options['port']}\")\n retry = 0\n while not self.mineflayer.is_running:\n print(\"Mineflayer process has exited, restarting\")\n self.mineflayer.run()\n if not self.mineflayer.is_running:\n if retry > 3:\n raise RuntimeError(\"Mineflayer process failed to start\")\n else:\n continue\n print(self.mineflayer.ready_line)\n res = requests.post(" + }, + { + "comment": "This code defines a class with methods for resetting the environment, stepping the Minecraft server, and returning observed data. It uses requests library to send HTTP POST requests to the server's \"start\" and \"step\" endpoints, checking status codes and handling errors accordingly. The class also has methods for pausing and unpausing the Minecraft server execution.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/env/bridge.py\":93-124", + "content": " f\"{self.server}/start\",\n json=self.reset_options,\n timeout=self.request_timeout,\n )\n if res.status_code != 200:\n self.mineflayer.stop()\n raise RuntimeError(\n f\"Minecraft server reply with code {res.status_code}\"\n )\n return res.json()\n def step(\n self,\n code: str,\n programs: str = \"\",\n ) -> Tuple[ObsType, SupportsFloat, bool, bool, Dict[str, Any]]:\n if not self.has_reset:\n raise RuntimeError(\"Environment has not been reset yet\")\n self.check_process()\n self.unpause()\n data = {\n \"code\": code,\n \"programs\": programs,\n }\n res = requests.post(\n f\"{self.server}/step\", json=data, timeout=self.request_timeout\n )\n if res.status_code != 200:\n raise RuntimeError(\"Failed to step Minecraft server\")\n returned_data = res.json()\n self.pause()\n return json.loads(returned_data)" + }, + { + "comment": "The code defines two methods, \"render\" and \"reset\". The \"render\" method raises a \"NotImplementedError\", indicating it's not implemented. The \"reset\" method takes optional seed and options parameters, sets reset options (port, mode, inventory, equipment, spread, waitTicks, position), stops the mineflayer, waits for it to exit, checks process status, and marks has_reset as True.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/env/bridge.py\":126-157", + "content": " def render(self):\n raise NotImplementedError(\"render is not implemented\")\n def reset(\n self,\n *,\n seed=None,\n options=None,\n ) -> Tuple[ObsType, Dict[str, Any]]:\n if options is None:\n options = {}\n if options.get(\"inventory\", {}) and options.get(\"mode\", \"hard\") != \"hard\":\n raise RuntimeError(\"inventory can only be set when options is hard\")\n self.reset_options = {\n \"port\": self.mc_port,\n \"reset\": options.get(\"mode\", \"hard\"),\n \"inventory\": options.get(\"inventory\", {}),\n \"equipment\": options.get(\"equipment\", []),\n \"spread\": options.get(\"spread\", False),\n \"waitTicks\": options.get(\"wait_ticks\", 5),\n \"position\": options.get(\"position\", None),\n }\n self.unpause()\n self.mineflayer.stop()\n time.sleep(1) # wait for mineflayer to exit\n returned_data = self.check_process()\n self.has_reset = True\n self.connected = True" + }, + { + "comment": "This code defines a class with methods to pause and unpause the server, stop the minecraft instance, and close the connection. The reset_options are set to \"soft\" and it returns parsed JSON data from an API request.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/env/bridge.py\":158-188", + "content": " # All the reset in step will be soft\n self.reset_options[\"reset\"] = \"soft\"\n self.pause()\n return json.loads(returned_data)\n def close(self):\n self.unpause()\n if self.connected:\n res = requests.post(f\"{self.server}/stop\")\n if res.status_code == 200:\n self.connected = False\n if self.mc_instance:\n self.mc_instance.stop()\n self.mineflayer.stop()\n return not self.connected\n def pause(self):\n if self.mineflayer.is_running and not self.server_paused:\n res = requests.post(f\"{self.server}/pause\")\n if res.status_code == 200:\n self.server_paused = True\n return self.server_paused\n def unpause(self):\n if self.mineflayer.is_running and self.server_paused:\n res = requests.post(f\"{self.server}/pause\")\n if res.status_code == 200:\n self.server_paused = False\n else:\n print(res.json())\n return self.server_paused" + } + ] +} \ No newline at end of file diff --git a/docs/doc/c36e1527-825e-4110-841f-3a9907da9d88.json b/docs/doc/c36e1527-825e-4110-841f-3a9907da9d88.json new file mode 100644 index 00000000..978f79a2 --- /dev/null +++ b/docs/doc/c36e1527-825e-4110-841f-3a9907da9d88.json @@ -0,0 +1,60 @@ +{ + "summary": "The ActionAgent class handles agent behavior, using ChatOpenAI for language processing and offering options like chat log and error handling. It defines methods to update chest memory, render observations, and send actions, while processing game events, constructing observation strings, handling inventory, checking chest involvement, and executing async functions with error handling and item filtering.", + "details": [ + { + "comment": "Initializing the ActionAgent class by setting its attributes and loading the necessary files. This class is used to control agent behavior, utilizing the ChatOpenAI language model for generating responses. It also has options for chat log, execution error handling, and saving/loading checkpoints for state persistence.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/agents/action.py\":0-33", + "content": "import re\nimport time\nimport voyager.utils as U\nfrom javascript import require\nfrom langchain.chat_models import ChatOpenAI\nfrom langchain.prompts import SystemMessagePromptTemplate\nfrom langchain.schema import AIMessage, HumanMessage, SystemMessage\nfrom voyager.prompts import load_prompt\nfrom voyager.control_primitives_context import load_control_primitives_context\nclass ActionAgent:\n def __init__(\n self,\n model_name=\"gpt-3.5-turbo\",\n temperature=0,\n request_timout=120,\n ckpt_dir=\"ckpt\",\n resume=False,\n chat_log=True,\n execution_error=True,\n ):\n self.ckpt_dir = ckpt_dir\n self.chat_log = chat_log\n self.execution_error = execution_error\n U.f_mkdir(f\"{ckpt_dir}/action\")\n if resume:\n print(f\"\\033[32mLoading Action Agent from {ckpt_dir}/action\\033[0m\")\n self.chest_memory = U.load_json(f\"{ckpt_dir}/action/chest_memory.json\")\n else:\n self.chest_memory = {}\n self.llm = ChatOpenAI(" + }, + { + "comment": "The code defines a class with methods for updating the agent's chest memory, rendering chest observations, and sending actions. It initializes an action agent using a model_name, temperature, and request_timeout. The update_chest_memory method checks if the chest already exists in the chest_memory dictionary and updates or removes it accordingly. If not present, it saves the new chest. Finally, the render_chest_observation method creates a list of chests from the chest memory and returns it.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/agents/action.py\":34-58", + "content": " model_name=model_name,\n temperature=temperature,\n request_timeout=request_timout,\n )\n def update_chest_memory(self, chests):\n for position, chest in chests.items():\n if position in self.chest_memory:\n if isinstance(chest, dict):\n self.chest_memory[position] = chest\n if chest == \"Invalid\":\n print(\n f\"\\033[32mAction Agent removing chest {position}: {chest}\\033[0m\"\n )\n self.chest_memory.pop(position)\n else:\n if chest != \"Invalid\":\n print(f\"\\033[32mAction Agent saving chest {position}: {chest}\\033[0m\")\n self.chest_memory[position] = chest\n U.dump_json(self.chest_memory, f\"{self.ckpt_dir}/action/chest_memory.json\")\n def render_chest_observation(self):\n chests = []\n for chest_position, chest in self.chest_memory.items():\n if isinstance(chest, dict) and len(chest) > 0:" + }, + { + "comment": "This code snippet is a part of the 'action.py' file in the Voyager project. It seems to be responsible for fetching information about chests from a data structure called 'chest_memory'. If a chest is empty, its status is updated accordingly. The function also checks if there are any unknown items inside a chest and updates its status as well. Finally, it returns the information on all chests found in the format of \"Chests: \". If no chests are found, it returns \"Chests: None\". Additionally, there's a comment indicating that this code may not work with a specific model_name (gpt-3.5-turbo).", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/agents/action.py\":59-85", + "content": " chests.append(f\"{chest_position}: {chest}\")\n for chest_position, chest in self.chest_memory.items():\n if isinstance(chest, dict) and len(chest) == 0:\n chests.append(f\"{chest_position}: Empty\")\n for chest_position, chest in self.chest_memory.items():\n if isinstance(chest, str):\n assert chest == \"Unknown\"\n chests.append(f\"{chest_position}: Unknown items inside\")\n assert len(chests) == len(self.chest_memory)\n if chests:\n chests = \"\\n\".join(chests)\n return f\"Chests:\\n{chests}\\n\\n\"\n else:\n return f\"Chests: None\\n\\n\"\n def render_system_message(self, skills=[]):\n system_template = load_prompt(\"action_template\")\n # FIXME: Hardcoded control_primitives\n base_skills = [\n \"exploreUntil\",\n \"mineBlock\",\n \"craftItem\",\n \"placeItem\",\n \"smeltItem\",\n \"killMob\",\n ]\n if not self.llm.model_name == \"gpt-3.5-turbo\":" + }, + { + "comment": "The code initializes system message components by joining base skills with additional skills, loading the response format template, creating a system message prompt, and formatting the system message with the joined programs and response format. The render_human_message function creates chat messages, error messages, and damage messages based on event types. It asserts that the last event is \"observe\".", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/agents/action.py\":86-111", + "content": " base_skills += [\n \"useChest\",\n \"mineflayer\",\n ]\n programs = \"\\n\\n\".join(load_control_primitives_context(base_skills) + skills)\n response_format = load_prompt(\"action_response_format\")\n system_message_prompt = SystemMessagePromptTemplate.from_template(\n system_template\n )\n system_message = system_message_prompt.format(\n programs=programs, response_format=response_format\n )\n assert isinstance(system_message, SystemMessage)\n return system_message\n def render_human_message(\n self, *, events, code=\"\", task=\"\", context=\"\", critique=\"\"\n ):\n chat_messages = []\n error_messages = []\n # FIXME: damage_messages is not used\n damage_messages = []\n assert events[-1][0] == \"observe\", \"Last event must be observe\"\n for i, (event_type, event) in enumerate(events):\n if event_type == \"onChat\":\n chat_messages.append(event[\"onChat\"])" + }, + { + "comment": "This code handles different event types in a game agent. It appends error and damage messages, collects observe data like biome, time of day, voxels, entities, health, hunger, position, equipment, inventory used, and the inventory itself. Then it constructs an observation string based on if there's code from the last round or not.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/agents/action.py\":112-134", + "content": " elif event_type == \"onError\":\n error_messages.append(event[\"onError\"])\n elif event_type == \"onDamage\":\n damage_messages.append(event[\"onDamage\"])\n elif event_type == \"observe\":\n biome = event[\"status\"][\"biome\"]\n time_of_day = event[\"status\"][\"timeOfDay\"]\n voxels = event[\"voxels\"]\n entities = event[\"status\"][\"entities\"]\n health = event[\"status\"][\"health\"]\n hunger = event[\"status\"][\"food\"]\n position = event[\"status\"][\"position\"]\n equipment = event[\"status\"][\"equipment\"]\n inventory_used = event[\"status\"][\"inventoryUsed\"]\n inventory = event[\"inventory\"]\n assert i == len(events) - 1, \"observe must be the last event\"\n observation = \"\"\n if code:\n observation += f\"Code from the last round:\\n{code}\\n\\n\"\n else:\n observation += f\"Code from the last round: No code in the first round\\n\\n\"" + }, + { + "comment": "This code snippet is part of a larger program that appears to be an AI agent. It generates an observation for the agent, including information about execution errors, chat logs, biome, time of day, nearby blocks and entities. The observation is constructed by iterating through various lists, joining them with newlines, and appending them to the \"observation\" string variable. If a list is empty, a corresponding message is added to the observation.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/agents/action.py\":136-165", + "content": " if self.execution_error:\n if error_messages:\n error = \"\\n\".join(error_messages)\n observation += f\"Execution error:\\n{error}\\n\\n\"\n else:\n observation += f\"Execution error: No error\\n\\n\"\n if self.chat_log:\n if chat_messages:\n chat_log = \"\\n\".join(chat_messages)\n observation += f\"Chat log: {chat_log}\\n\\n\"\n else:\n observation += f\"Chat log: None\\n\\n\"\n observation += f\"Biome: {biome}\\n\\n\"\n observation += f\"Time: {time_of_day}\\n\\n\"\n if voxels:\n observation += f\"Nearby blocks: {', '.join(voxels)}\\n\\n\"\n else:\n observation += f\"Nearby blocks: None\\n\\n\"\n if entities:\n nearby_entities = [\n k for k, v in sorted(entities.items(), key=lambda x: x[1])\n ]\n observation += f\"Nearby entities (nearest to farthest): {', '.join(nearby_entities)}\\n\\n\"\n else:\n observation += f\"Nearby entities (nearest to farthest): None\\n\\n\"" + }, + { + "comment": "This code generates an observation string containing the agent's health, hunger, position, equipment, inventory, and task. It also checks if a chest is involved in the current task, adds chest information if necessary, and includes context and critique if available. The final result is returned as a HumanMessage object.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/agents/action.py\":167-198", + "content": " observation += f\"Health: {health:.1f}/20\\n\\n\"\n observation += f\"Hunger: {hunger:.1f}/20\\n\\n\"\n observation += f\"Position: x={position['x']:.1f}, y={position['y']:.1f}, z={position['z']:.1f}\\n\\n\"\n observation += f\"Equipment: {equipment}\\n\\n\"\n if inventory:\n observation += f\"Inventory ({inventory_used}/36): {inventory}\\n\\n\"\n else:\n observation += f\"Inventory ({inventory_used}/36): Empty\\n\\n\"\n if not (\n task == \"Place and deposit useless items into a chest\"\n or task.startswith(\"Deposit useless items into the chest at\")\n ):\n observation += self.render_chest_observation()\n observation += f\"Task: {task}\\n\\n\"\n if context:\n observation += f\"Context: {context}\\n\\n\"\n else:\n observation += f\"Context: None\\n\\n\"\n if critique:\n observation += f\"Critique: {critique}\\n\\n\"\n else:\n observation += f\"Critique: None\\n\\n\"\n return HumanMessage(content=observation)" + }, + { + "comment": "The code defines a function process_ai_message, which takes an AIMessage object as input. It checks if the message is of correct type, then enters a while loop to handle potential errors. Inside the loop, it imports required libraries and compiles the JavaScript/JS code from the message content using Babel. It parses the compiled code and extracts function declarations from the program's body. If no functions are found, it raises an assertion error. The extracted functions are then stored in a list. This function seems to be part of an AI agent for processing messages containing JavaScript/JS code snippets.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/agents/action.py\":200-225", + "content": " def process_ai_message(self, message):\n assert isinstance(message, AIMessage)\n retry = 3\n error = None\n while retry > 0:\n try:\n babel = require(\"@babel/core\")\n babel_generator = require(\"@babel/generator\").default\n code_pattern = re.compile(r\"```(?:javascript|js)(.*?)```\", re.DOTALL)\n code = \"\\n\".join(code_pattern.findall(message.content))\n parsed = babel.parse(code)\n functions = []\n assert len(list(parsed.program.body)) > 0, \"No functions found\"\n for i, node in enumerate(parsed.program.body):\n if node.type != \"FunctionDeclaration\":\n continue\n node_type = (\n \"AsyncFunctionDeclaration\"\n if node[\"async\"]\n else \"FunctionDeclaration\"\n )\n functions.append(\n {\n \"name\": node.id.name," + }, + { + "comment": "This code finds the last async function in a list of functions, asserts that there is one and it takes a single argument named \"bot\", then joins the bodies of all functions into program_code. It then creates an executable line of code with the main_function name and awaits its execution.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/agents/action.py\":226-245", + "content": " \"type\": node_type,\n \"body\": babel_generator(node).code,\n \"params\": list(node[\"params\"]),\n }\n )\n # find the last async function\n main_function = None\n for function in reversed(functions):\n if function[\"type\"] == \"AsyncFunctionDeclaration\":\n main_function = function\n break\n assert (\n main_function is not None\n ), \"No async function found. Your main function must be async.\"\n assert (\n len(main_function[\"params\"]) == 1\n and main_function[\"params\"][0].name == \"bot\"\n ), f\"Main function {main_function['name']} must take a single argument named 'bot'\"\n program_code = \"\\n\\n\".join(function[\"body\"] for function in functions)\n exec_code = f\"await {main_function['name']}(bot);\"" + }, + { + "comment": "The code defines a function that parses action responses and filters chat log messages for specific item requirements before program execution. It also includes error handling and retry mechanisms in case of exceptions during parsing. The chat log filtering uses regular expressions to match patterns and extract the required items.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/agents/action.py\":246-269", + "content": " return {\n \"program_code\": program_code,\n \"program_name\": main_function[\"name\"],\n \"exec_code\": exec_code,\n }\n except Exception as e:\n retry -= 1\n error = e\n time.sleep(1)\n return f\"Error parsing action response (before program execution): {error}\"\n def summarize_chatlog(self, events):\n def filter_item(message: str):\n craft_pattern = r\"I cannot make \\w+ because I need: (.*)\"\n craft_pattern2 = (\n r\"I cannot make \\w+ because there is no crafting table nearby\"\n )\n mine_pattern = r\"I need at least a (.*) to mine \\w+!\"\n if re.match(craft_pattern, message):\n return re.match(craft_pattern, message).groups()[0]\n elif re.match(craft_pattern2, message):\n return \"a nearby crafting table\"\n elif re.match(mine_pattern, message):\n return re.match(mine_pattern, message).groups()[0]" + }, + { + "comment": "Code checks for events of type \"onChat\", filters the items, and adds them to chatlog. If chatlog is not empty, returns a message mentioning required items. Otherwise, returns an empty string.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/agents/action.py\":270-279", + "content": " else:\n return \"\"\n chatlog = set()\n for event_type, event in events:\n if event_type == \"onChat\":\n item = filter_item(event[\"onChat\"])\n if item:\n chatlog.add(item)\n return \"I also need \" + \", \".join(chatlog) + \".\" if chatlog else \"\"" + } + ] +} \ No newline at end of file diff --git a/docs/doc/cfefe072-d2b9-4810-a4ea-6a2c15e62c54.json b/docs/doc/cfefe072-d2b9-4810-a4ea-6a2c15e62c54.json new file mode 100644 index 00000000..49434e5c --- /dev/null +++ b/docs/doc/cfefe072-d2b9-4810-a4ea-6a2c15e62c54.json @@ -0,0 +1,25 @@ +{ + "summary": "The EventRecorder class records events, updates elapsed time and item history, and maintains dictionaries for item-time associations and iteration items. It has a resume method and sorts records based on timestamps.", + "details": [ + { + "comment": "Class EventRecorder initializes attributes, sets up a directory for event files, and handles resuming or starting fresh. It records events for tasks, storing information about each event type in various dictionaries for later use.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/utils/record_utils.py\":0-37", + "content": "import time\nfrom .file_utils import *\nfrom .json_utils import *\nclass EventRecorder:\n def __init__(\n self,\n ckpt_dir=\"ckpt\",\n resume=False,\n init_position=None,\n ):\n self.ckpt_dir = ckpt_dir\n self.item_history = set()\n self.item_vs_time = {}\n self.item_vs_iter = {}\n self.biome_history = set()\n self.init_position = init_position\n self.position_history = [[0, 0]]\n self.elapsed_time = 0\n self.iteration = 0\n f_mkdir(self.ckpt_dir, \"events\")\n if resume:\n self.resume()\n def record(self, events, task):\n task = re.sub(r'[\\\\/:\"*?<>| ]', \"_\", task)\n task = task.replace(\" \", \"_\") + time.strftime(\n \"_%Y%m%d_%H%M%S\", time.localtime()\n )\n self.iteration += 1\n if not self.init_position:\n self.init_position = [\n events[0][1][\"status\"][\"position\"][\"x\"],\n events[0][1][\"status\"][\"position\"][\"z\"],\n ]\n for event_type, event in events:" + }, + { + "comment": "The code is part of a Recorder class responsible for recording events. It updates elapsed time and iteration, prints messages, dumps JSON data to disk, and provides a resume method to continue from a specific cutoff point. The get_timestamp function helps sort the records based on timestamps.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/utils/record_utils.py\":38-63", + "content": " self.update_items(event)\n if event_type == \"observe\":\n self.update_elapsed_time(event)\n print(\n f\"\\033[96m****Recorder message: {self.elapsed_time} ticks have elapsed****\\033[0m\\n\"\n f\"\\033[96m****Recorder message: {self.iteration} iteration passed****\\033[0m\"\n )\n dump_json(events, f_join(self.ckpt_dir, \"events\", task))\n def resume(self, cutoff=None):\n self.item_history = set()\n self.item_vs_time = {}\n self.item_vs_iter = {}\n self.elapsed_time = 0\n self.position_history = [[0, 0]]\n def get_timestamp(string):\n timestamp = \"_\".join(string.split(\"_\")[-2:])\n return time.mktime(time.strptime(timestamp, \"%Y%m%d_%H%M%S\"))\n records = f_listdir(self.ckpt_dir, \"events\")\n sorted_records = sorted(records, key=get_timestamp)\n for record in sorted_records:\n self.iteration += 1\n if cutoff and self.iteration > cutoff:\n break" + }, + { + "comment": "This code loads event data and updates the position, items, and elapsed time based on each event type. It also maintains a history of items and biomes encountered over time. New items are added to the item_vs_time dictionary, which tracks when they were first encountered.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/utils/record_utils.py\":64-87", + "content": " events = load_json(f_join(self.ckpt_dir, \"events\", record))\n if not self.init_position:\n self.init_position = (\n events[0][1][\"status\"][\"position\"][\"x\"],\n events[0][1][\"status\"][\"position\"][\"z\"],\n )\n for event_type, event in events:\n self.update_items(event)\n self.update_position(event)\n if event_type == \"observe\":\n self.update_elapsed_time(event)\n def update_items(self, event):\n inventory = event[\"inventory\"]\n elapsed_time = event[\"status\"][\"elapsedTime\"]\n biome = event[\"status\"][\"biome\"]\n items = set(inventory.keys())\n new_items = items - self.item_history\n self.item_history.update(items)\n self.biome_history.add(biome)\n if new_items:\n if self.elapsed_time + elapsed_time not in self.item_vs_time:\n self.item_vs_time[self.elapsed_time + elapsed_time] = []\n self.item_vs_time[self.elapsed_time + elapsed_time].extend(new_items)" + }, + { + "comment": "The `update_position` function checks if the position has changed and adds it to the `position_history` list. The `update_elapsed_time` function increments the elapsed time with the event's elapsed time. The `item_vs_iter` dictionary stores the items associated with each iteration, and if a new iteration is encountered, it initializes an empty list for that iteration in the dictionary.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/utils/record_utils.py\":88-101", + "content": " if self.iteration not in self.item_vs_iter:\n self.item_vs_iter[self.iteration] = []\n self.item_vs_iter[self.iteration].extend(new_items)\n def update_elapsed_time(self, event):\n self.elapsed_time += event[\"status\"][\"elapsedTime\"]\n def update_position(self, event):\n position = [\n event[\"status\"][\"position\"][\"x\"] - self.init_position[0],\n event[\"status\"][\"position\"][\"z\"] - self.init_position[1],\n ]\n if self.position_history[-1] != position:\n self.position_history.append(position)" + } + ] +} \ No newline at end of file diff --git a/docs/doc/da233986-26ab-483b-99f4-35172a868778.json b/docs/doc/da233986-26ab-483b-99f4-35172a868778.json new file mode 100644 index 00000000..2d67941b --- /dev/null +++ b/docs/doc/da233986-26ab-483b-99f4-35172a868778.json @@ -0,0 +1,20 @@ +{ + "summary": "This code manages a subprocess, defining a class with methods to control its lifecycle and using threading for concurrency. It initializes subprocesses with commands, sets up event handlers for readiness and completion, and monitors output for specific patterns to call callback functions.", + "details": [ + { + "comment": "Class for monitoring subprocesses, taking a list of commands, name, regex patterns for ready and callback conditions, log path, and optional callbacks on process completion. Sets up a logger with time-stamped file handler and initializes process variable.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/env/process_monitor.py\":0-37", + "content": "import time\nimport re\nimport warnings\nfrom typing import List\nimport psutil\nimport subprocess\nimport logging\nimport threading\nimport voyager.utils as U\nclass SubprocessMonitor:\n def __init__(\n self,\n commands: List[str],\n name: str,\n ready_match: str = r\".*\",\n log_path: str = \"logs\",\n callback_match: str = r\"^(?!x)x$\", # regex that will never match\n callback: callable = None,\n finished_callback: callable = None,\n ):\n self.commands = commands\n start_time = time.strftime(\"%Y%m%d_%H%M%S\")\n self.name = name\n self.logger = logging.getLogger(name)\n handler = logging.FileHandler(U.f_join(log_path, f\"{start_time}.log\"))\n formatter = logging.Formatter(\n \"%(asctime)s - %(name)s - %(levelname)s - %(message)s\"\n )\n handler.setFormatter(formatter)\n self.logger.addHandler(handler)\n self.logger.setLevel(logging.INFO)\n self.process = None\n self.ready_match = ready_match\n self.ready_event = None" + }, + { + "comment": "This code initializes a subprocess with commands and sets up event handlers for its readiness and completion. It monitors the output of the subprocess, looking for specific patterns in each line to determine when it's ready and to call the callback function. If the process fails to start, it raises a warning.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/env/process_monitor.py\":38-64", + "content": " self.ready_line = None\n self.callback_match = callback_match\n self.callback = callback\n self.finished_callback = finished_callback\n self.thread = None\n def _start(self):\n self.logger.info(f\"Starting subprocess with commands: {self.commands}\")\n self.process = psutil.Popen(\n self.commands,\n stdout=subprocess.PIPE,\n stderr=subprocess.STDOUT,\n universal_newlines=True,\n )\n print(f\"Subprocess {self.name} started with PID {self.process.pid}.\")\n for line in iter(self.process.stdout.readline, \"\"):\n self.logger.info(line.strip())\n if re.search(self.ready_match, line):\n self.ready_line = line\n self.logger.info(\"Subprocess is ready.\")\n self.ready_event.set()\n if re.search(self.callback_match, line):\n self.callback()\n if not self.ready_event.is_set():\n self.ready_event.set()\n warnings.warn(f\"Subprocess {self.name} failed to start.\")" + }, + { + "comment": "This code defines a class that manages a subprocess and provides methods to control its lifecycle, including starting, stopping, and checking if it's still running. It uses the threading module for concurrency and a property to determine if the process is running.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/env/process_monitor.py\":65-89", + "content": " if self.finished_callback:\n self.finished_callback()\n def run(self):\n self.ready_event = threading.Event()\n self.ready_line = None\n self.thread = threading.Thread(target=self._start)\n self.thread.start()\n self.ready_event.wait()\n def stop(self):\n self.logger.info(\"Stopping subprocess.\")\n if self.process and self.process.is_running():\n self.process.terminate()\n self.process.wait()\n # def __del__(self):\n # if self.process.is_running():\n # self.stop()\n @property\n def is_running(self):\n if self.process is None:\n return False\n return self.process.is_running()" + } + ] +} \ No newline at end of file diff --git a/docs/doc/ddd82a35-75f5-4a73-a12e-6737eeec2c34.json b/docs/doc/ddd82a35-75f5-4a73-a12e-6737eeec2c34.json new file mode 100644 index 00000000..72e980e3 --- /dev/null +++ b/docs/doc/ddd82a35-75f5-4a73-a12e-6737eeec2c34.json @@ -0,0 +1,20 @@ +{ + "summary": "Function `waitForMobRemoved` waits for entity removal, stops attacking on timeout, resolves with item or rejects with error using event listeners.", + "details": [ + { + "comment": "Function waitForMobRemoved sets up a timeout to check if a specified entity is removed, handling entityRemoved event, stops attacking when timeout ends, and notifies upon successful removal.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/control_primitives/waitForMobRemoved.js\":0-30", + "content": "function waitForMobRemoved(bot, entity, timeout = 300) {\n return new Promise((resolve, reject) => {\n let success = false;\n let droppedItem = null;\n // Set up timeout\n const timeoutId = setTimeout(() => {\n success = false;\n bot.pvp.stop();\n }, timeout * 1000);\n // Function to handle entityRemoved event\n function onEntityGone(e) {\n if (e === entity) {\n success = true;\n clearTimeout(timeoutId);\n bot.chat(`Killed ${entity.name}!`);\n bot.pvp.stop();\n }\n }\n function onItemDrop(item) {\n if (entity.position.distanceTo(item.position) <= 1) {\n droppedItem = item;\n }\n }\n function onStoppedAttacking() {\n clearTimeout(timeoutId);\n bot.removeListener(\"entityGone\", onEntityGone);\n bot.removeListener(\"stoppedAttacking\", onStoppedAttacking);\n bot.removeListener(\"itemDrop\", onItemDrop);" + }, + { + "comment": "This code sets up a timeout and event listeners to wait for the entity to be killed. It resolves with the dropped item if successful, or rejects with an error if it fails. The `onEntityGone` function handles the \"entityRemoved\" event, and the `onItemDrop` function handles the \"itemDrop\" event.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/control_primitives/waitForMobRemoved.js\":31-64", + "content": " if (!success) reject(new Error(`Failed to kill ${entity.name}.`));\n else resolve(droppedItem);\n }\n // Listen for entityRemoved event\n bot.on(\"entityGone\", onEntityGone);\n bot.on(\"stoppedAttacking\", onStoppedAttacking);\n bot.on(\"itemDrop\", onItemDrop);\n });\n}\nfunction waitForMobShot(bot, entity, timeout = 300) {\n return new Promise((resolve, reject) => {\n let success = false;\n let droppedItem = null;\n // Set up timeout\n const timeoutId = setTimeout(() => {\n success = false;\n bot.hawkEye.stop();\n }, timeout * 1000);\n // Function to handle entityRemoved event\n function onEntityGone(e) {\n if (e === entity) {\n success = true;\n clearTimeout(timeoutId);\n bot.chat(`Shot ${entity.name}!`);\n bot.hawkEye.stop();\n }\n }\n function onItemDrop(item) {\n if (entity.position.distanceTo(item.position) <= 1) {" + }, + { + "comment": "Function `waitForMobRemoved` uses event listeners to detect when an entity is removed, stopped auto shot, or an item is dropped. It resolves with the dropped item if successful, otherwise rejects with an error.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/control_primitives/waitForMobRemoved.js\":65-83", + "content": " droppedItem = item;\n }\n }\n function onAutoShotStopped() {\n clearTimeout(timeoutId);\n bot.removeListener(\"entityGone\", onEntityGone);\n bot.removeListener(\"auto_shot_stopped\", onAutoShotStopped);\n bot.removeListener(\"itemDrop\", onItemDrop);\n if (!success) reject(new Error(`Failed to shoot ${entity.name}.`));\n else resolve(droppedItem);\n }\n // Listen for entityRemoved event\n bot.on(\"entityGone\", onEntityGone);\n bot.on(\"auto_shot_stopped\", onAutoShotStopped);\n bot.on(\"itemDrop\", onItemDrop);\n });\n}" + } + ] +} \ No newline at end of file diff --git a/docs/doc/e1d9c81f-0050-487f-bf6d-88d578f0af51.json b/docs/doc/e1d9c81f-0050-487f-bf6d-88d578f0af51.json new file mode 100644 index 00000000..969d343a --- /dev/null +++ b/docs/doc/e1d9c81f-0050-487f-bf6d-88d578f0af51.json @@ -0,0 +1,10 @@ +{ + "summary": "This function loads control primitives context from the \"voyager\" package. It first determines the package path, then checks for primitive names in the directory if none are provided. It retrieves and returns a list of loaded text primitives.", + "details": [ + { + "comment": "This function loads control primitives context from the \"voyager\" package. It first determines the package path, then checks for primitive names in the directory if none are provided. It retrieves and returns a list of loaded text primitives.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/control_primitives_context/__init__.py\":0-17", + "content": "import pkg_resources\nimport os\nimport voyager.utils as U\ndef load_control_primitives_context(primitive_names=None):\n package_path = pkg_resources.resource_filename(\"voyager\", \"\")\n if primitive_names is None:\n primitive_names = [\n primitive[:-3]\n for primitive in os.listdir(f\"{package_path}/control_primitives_context\")\n if primitive.endswith(\".js\")\n ]\n primitives = [\n U.load_text(f\"{package_path}/control_primitives_context/{primitive_name}.js\")\n for primitive_name in primitive_names\n ]\n return primitives" + } + ] +} \ No newline at end of file diff --git a/docs/doc/e1dba2d5-a5b6-467b-a980-ac15c5c0e6b4.json b/docs/doc/e1dba2d5-a5b6-467b-a980-ac15c5c0e6b4.json new file mode 100644 index 00000000..bf9e8219 --- /dev/null +++ b/docs/doc/e1dba2d5-a5b6-467b-a980-ac15c5c0e6b4.json @@ -0,0 +1,10 @@ +{ + "summary": "This code defines a main function named \"yourMainFunctionName\" that takes a parameter called \"bot\". The function is async, meaning it can perform asynchronous operations. It includes helper functions, which are only used if needed and should be avoided when possible. The code also has additional lines of code that may contain instructions or prompts.", + "details": [ + { + "comment": "This code defines a main function named \"yourMainFunctionName\" that takes a parameter called \"bot\". The function is async, meaning it can perform asynchronous operations. It includes helper functions, which are only used if needed and should be avoided when possible. The code also has additional lines of code that may contain instructions or prompts.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/prompts/action_response_format.txt\":0-14", + "content": "Explain: ...\nPlan:\n1) ...\n2) ...\n3) ...\n...\nCode:\n```javascript\n// helper functions (only if needed, try to avoid them)\n...\n// main function after the helper functions\nasync function yourMainFunctionName(bot) {\n // ...\n}\n```" + } + ] +} \ No newline at end of file diff --git a/docs/doc/e2d85362-4b1d-4fa2-aea2-d7ac03424c59.json b/docs/doc/e2d85362-4b1d-4fa2-aea2-d7ac03424c59.json new file mode 100644 index 00000000..1ee042db --- /dev/null +++ b/docs/doc/e2d85362-4b1d-4fa2-aea2-d7ac03424c59.json @@ -0,0 +1,10 @@ +{ + "summary": "This code imports different agents from their respective modules (action, critic, curriculum) and SkillManager for Voyager's agent functionality.", + "details": [ + { + "comment": "This code imports different agents from their respective modules (action, critic, curriculum) and SkillManager for Voyager's agent functionality.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/agents/__init__.py\":0-3", + "content": "from .action import ActionAgent\nfrom .critic import CriticAgent\nfrom .curriculum import CurriculumAgent\nfrom .skill import SkillManager" + } + ] +} \ No newline at end of file diff --git a/docs/doc/ea351896-e43f-4ee6-9312-5507e68d2aa0.json b/docs/doc/ea351896-e43f-4ee6-9312-5507e68d2aa0.json new file mode 100644 index 00000000..fca8d398 --- /dev/null +++ b/docs/doc/ea351896-e43f-4ee6-9312-5507e68d2aa0.json @@ -0,0 +1,20 @@ +{ + "summary": "The function placeItem verifies item existence, position, and equipment before placement. It handles inventory count mismatches, saves upon success, and reports errors.", + "details": [ + { + "comment": "This function, placeItem, accepts three parameters: bot, name, and position. It checks if the name is a string and the position is a Vec3. If not, it throws an error. The function then verifies that there is an item with the given name and checks if it exists in the bot's inventory. If not, it notifies via chat and returns. If everything is fine, it searches for a reference block based on faceVectors, iterating through six possible vectors to find a suitable position to place the item.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/control_primitives/placeItem.js\":0-31", + "content": "async function placeItem(bot, name, position) {\n // return if name is not string\n if (typeof name !== \"string\") {\n throw new Error(`name for placeItem must be a string`);\n }\n // return if position is not Vec3\n if (!(position instanceof Vec3)) {\n throw new Error(`position for placeItem must be a Vec3`);\n }\n const itemByName = mcData.itemsByName[name];\n if (!itemByName) {\n throw new Error(`No item named ${name}`);\n }\n const item = bot.inventory.findInventoryItem(itemByName.id);\n if (!item) {\n bot.chat(`No ${name} in inventory`);\n return;\n }\n const item_count = item.count;\n // find a reference block\n const faceVectors = [\n new Vec3(0, 1, 0),\n new Vec3(0, -1, 0),\n new Vec3(1, 0, 0),\n new Vec3(-1, 0, 0),\n new Vec3(0, 0, 1),\n new Vec3(0, 0, -1),\n ];\n let referenceBlock = null;\n let faceVector = null;\n for (const vector of faceVectors) {\n const block = bot.blockAt(position.minus(vector));" + }, + { + "comment": "This code checks if there's a block to place an item on, then goes to the target block position, equips the item, and places it. If no block is found or placing fails too many times, it reports an error.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/control_primitives/placeItem.js\":32-61", + "content": " if (block?.name !== \"air\") {\n referenceBlock = block;\n faceVector = vector;\n bot.chat(`Placing ${name} on ${block.name} at ${block.position}`);\n break;\n }\n }\n if (!referenceBlock) {\n bot.chat(\n `No block to place ${name} on. You cannot place a floating block.`\n );\n _placeItemFailCount++;\n if (_placeItemFailCount > 10) {\n throw new Error(\n `placeItem failed too many times. You cannot place a floating block.`\n );\n }\n return;\n }\n // You must use try catch to placeBlock\n try {\n // You must first go to the block position you want to place\n await bot.pathfinder.goto(new GoalPlaceBlock(position, bot.world, {}));\n // You must equip the item right before calling placeBlock\n await bot.equip(item, \"hand\");\n await bot.placeBlock(referenceBlock, faceVector);\n bot.chat(`Placed ${name}`);\n bot.save(`${name}_placed`);\n } catch (err) {" + }, + { + "comment": "Checks if the item count in inventory matches, handles failure and displays error message, and saves when successfully placed.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/control_primitives/placeItem.js\":62-78", + "content": " const item = bot.inventory.findInventoryItem(itemByName.id);\n if (item?.count === item_count) {\n bot.chat(\n `Error placing ${name}: ${err.message}, please find another position to place`\n );\n _placeItemFailCount++;\n if (_placeItemFailCount > 10) {\n throw new Error(\n `placeItem failed too many times, please find another position to place.`\n );\n }\n } else {\n bot.chat(`Placed ${name}`);\n bot.save(`${name}_placed`);\n }\n }\n}" + } + ] +} \ No newline at end of file diff --git a/docs/doc/edfdc7a1-f55b-473e-987d-39182a3e63b6.json b/docs/doc/edfdc7a1-f55b-473e-987d-39182a3e63b6.json new file mode 100644 index 00000000..746e06bf --- /dev/null +++ b/docs/doc/edfdc7a1-f55b-473e-987d-39182a3e63b6.json @@ -0,0 +1,25 @@ +{ + "summary": "This code evaluates Minecraft tasks by analyzing user input, inventory data, and offers feedback on progress. It manages tasks like planting, zombie killing, and food eating, checks inventory for success or failure, and suggests item removal if over 20 slots are occupied.", + "details": [ + { + "comment": "This code outlines the required information and context for assessing progress in Minecraft tasks, including biome, time, nearby blocks, health, hunger, position, equipment, inventory, chests, and task.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/prompts/critic.txt\":0-15", + "content": "You are an assistant that assesses my progress of playing Minecraft and provides useful guidance.\nYou are required to evaluate if I have met the task requirements. Exceeding the task requirements is also considered a success while failing to meet them requires you to provide critique to help me improve.\nI will give you the following information:\nBiome: The biome after the task execution.\nTime: The current time.\nNearby blocks: The surrounding blocks. These blocks are not collected yet. However, this is useful for some placing or planting tasks.\nHealth: My current health.\nHunger: My current hunger level. For eating task, if my hunger level is 20.0, then I successfully ate the food.\nPosition: My current position.\nEquipment: My final equipment. For crafting tasks, I sometimes equip the crafted item.\nInventory (xx/36): My final inventory. For mining and smelting tasks, you only need to check inventory.\nChests: If the task requires me to place items in a chest, you can find chest information here.\nTask: The objective I need to accomplish." + }, + { + "comment": "Code provides instructions for generating a JSON response based on user input and inventory information, evaluating task completion and offering critique if necessary.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/prompts/critic.txt\":16-58", + "content": "Context: The context of the task.\nYou should only respond in JSON format as described below:\n{\n \"reasoning\": \"reasoning\",\n \"success\": boolean,\n \"critique\": \"critique\",\n}\nEnsure the response can be parsed by Python `json.loads`, e.g.: no trailing commas, no single quotes, etc.\nHere are some examples:\nINPUT:\nInventory (2/36): {'oak_log':2, 'spruce_log':2}\nTask: Mine 3 wood logs\nRESPONSE:\n{\n \"reasoning\": \"You need to mine 3 wood logs. You have 2 oak logs and 2 spruce logs, which add up to 4 wood logs.\",\n \"success\": true,\n \"critique\": \"\"\n}\nINPUT:\nInventory (3/36): {'crafting_table': 1, 'spruce_planks': 6, 'stick': 4}\nTask: Craft a wooden pickaxe\nRESPONSE:\n{\n \"reasoning\": \"You have enough materials to craft a wooden pickaxe, but you didn't craft it.\",\n \"success\": false,\n \"critique\": \"Craft a wooden pickaxe with a crafting table using 3 spruce planks and 2 sticks.\"\n}\nINPUT:\nInventory (2/36): {'raw_iron': 5, 'stone_pickaxe': 1}\nTask: Mine 5 iron_ore\nRESPONSE:\n{\n \"reasoning\": \"Mining iron_ore in Minecraft will get raw_iron. You have 5 raw_iron in your inventory.\"," + }, + { + "comment": "This code handles different game tasks such as planting, killing zombies, and eating food. It checks the player's inventory, nearby blocks, or hunger level to determine if the task was successful or not. The reasoning behind each success is provided along with a \"success\" flag and an empty \"critique\" field.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/prompts/critic.txt\":59-112", + "content": " \"success\": true,\n \"critique\": \"\"\n}\nINPUT:\nBiome: plains\nNearby blocks: stone, dirt, grass_block, grass, farmland, wheat\nInventory (26/36): ...\nTask: Plant 1 wheat seed.\nRESPONSE:\n{\n \"reasoning\": \"For planting tasks, inventory information is useless. In nearby blocks, there is farmland and wheat, which means you succeed to plant the wheat seed.\",\n \"success\": true,\n \"critique\": \"\"\n}\nINPUT:\nInventory (11/36): {... ,'rotten_flesh': 1}\nTask: Kill 1 zombie\nContext: ...\nRESPONSE\n{\n \"reasoning\": \"You have rotten flesh in your inventory, which means you successfully killed one zombie.\",\n \"success\": true,\n \"critique\": \"\"\n}\nINPUT:\nHunger: 20.0/20.0\nInventory (11/36): ...\nTask: Eat 1 ...\nContext: ...\nRESPONSE\n{\n \"reasoning\": \"For all eating task, if the player's hunger is 20.0, then the player successfully ate the food.\",\n \"success\": true,\n \"critique\": \"\"\n}\nINPUT:\nNearby blocks: chest\nInventory (28/36): {'rail': 1, 'coal': 2, 'oak_planks': 13, 'copper_block': 1, 'diorite': 7, 'cooked_beef': 4, 'gran" + }, + { + "comment": "Code snippet represents inventory items with their counts, chest location and associated task to deposit useless items. A response is given if the player has more than 20 slots occupied in their inventory, suggesting specific items to remove for compliance.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/prompts/critic.txt\":112-126", + "content": "ite': 22, 'cobbled_deepslate': 23, 'feather': 4, 'leather': 2, 'cooked_chicken': 3, 'white_wool': 2, 'stick': 3, 'black_wool': 1, 'stone_sword': 2, 'stone_hoe': 1, 'stone_axe': 2, 'stone_shovel': 2, 'cooked_mutton': 4, 'cobblestone_wall': 18, 'crafting_table': 1, 'furnace': 1, 'iron_pickaxe': 1, 'stone_pickaxe': 1, 'raw_copper': 12}\nChests:\n(81, 131, 16): {'andesite': 2, 'dirt': 2, 'cobblestone': 75, 'wooden_pickaxe': 1, 'wooden_sword': 1}\nTask: Deposit useless items into the chest at (81, 131, 16)\nContext: ...\nRESPONSE\n{\n \"reasoning\": \"You have 28 items in your inventory after depositing, which is more than 20. You need to deposit more items from your inventory to the chest.\",\n \"success\": false,\n \"critique\": \"Deposit more useless items such as copper_block, diorite, granite, cobbled_deepslate, feather, and leather to meet the requirement of having only 20 occupied slots in your inventory.\"\n}" + } + ] +} \ No newline at end of file diff --git a/docs/doc/ee357c24-4730-4532-98f2-344e33b303fd.json b/docs/doc/ee357c24-4730-4532-98f2-344e33b303fd.json new file mode 100644 index 00000000..1384d131 --- /dev/null +++ b/docs/doc/ee357c24-4730-4532-98f2-344e33b303fd.json @@ -0,0 +1,10 @@ +{ + "summary": "This code imports required packages, defines a function to load prompts using the voyager.utils module, and uses pkg_resources to obtain the package's file path. It then returns the prompt text using the U.load_text() method.", + "details": [ + { + "comment": "This code imports required packages, defines a function to load prompts using the voyager.utils module, and uses pkg_resources to obtain the package's file path. It then returns the prompt text using the U.load_text() method.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/prompts/__init__.py\":0-6", + "content": "import pkg_resources\nimport voyager.utils as U\ndef load_prompt(prompt):\n package_path = pkg_resources.resource_filename(\"voyager\", \"\")\n return U.load_text(f\"{package_path}/prompts/{prompt}.txt\")" + } + ] +} \ No newline at end of file diff --git a/docs/doc/f32f220b-7eb9-48db-a18e-d34ece65e9f2.json b/docs/doc/f32f220b-7eb9-48db-a18e-d34ece65e9f2.json new file mode 100644 index 00000000..a17fd7a6 --- /dev/null +++ b/docs/doc/f32f220b-7eb9-48db-a18e-d34ece65e9f2.json @@ -0,0 +1,15 @@ +{ + "summary": "The function places an item near the player, finds the inventory item by name, searches for a reference block to place it on, determines position and facing direction, uses pathfinder to go there, and finally places the item. The code snippet equips an item in the bot's hand and then places a block relative to a reference block.", + "details": [ + { + "comment": "This function places an item near the player. It first finds the inventory item by name, then searches for a reference block to place it on. After finding a suitable position and facing direction, the bot goes to that position using the pathfinder, and finally places the item.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/control_primitives_context/placeItem.js\":0-24", + "content": "// Place a crafting_table near the player, Vec3(1, 0, 0) is just an example, you shouldn't always use that: placeItem(bot, \"crafting_table\", bot.entity.position.offset(1, 0, 0));\nasync function placeItem(bot, name, position) {\n const item = bot.inventory.findInventoryItem(mcData.itemsByName[name].id);\n // find a reference block\n const faceVectors = [\n new Vec3(0, 1, 0),\n new Vec3(0, -1, 0),\n new Vec3(1, 0, 0),\n new Vec3(-1, 0, 0),\n new Vec3(0, 0, 1),\n new Vec3(0, 0, -1),\n ];\n let referenceBlock = null;\n let faceVector = null;\n for (const vector of faceVectors) {\n const block = bot.blockAt(position.minus(vector));\n if (block?.name !== \"air\") {\n referenceBlock = block;\n faceVector = vector;\n break;\n }\n }\n // You must first go to the block position you want to place\n await bot.pathfinder.goto(new GoalPlaceBlock(position, bot.world, {}));\n // You must equip the item right before calling placeBlock" + }, + { + "comment": "The code snippet equips an item in the bot's hand and then places a block relative to a reference block.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/control_primitives_context/placeItem.js\":25-27", + "content": " await bot.equip(item, \"hand\");\n await bot.placeBlock(referenceBlock, faceVector);\n}" + } + ] +} \ No newline at end of file diff --git a/docs/doc/f6af8035-1d82-444e-ad76-605490cbb362.json b/docs/doc/f6af8035-1d82-444e-ad76-605490cbb362.json new file mode 100644 index 00000000..cbe60531 --- /dev/null +++ b/docs/doc/f6af8035-1d82-444e-ad76-605490cbb362.json @@ -0,0 +1,15 @@ +{ + "summary": "The code introduces the Skill Library and its community contributions for a project, providing instructions on resuming from a community contribution and contributing after running the learning process. It also outlines how to share the learned skill library.", + "details": [ + { + "comment": "This code introduces the Skill Library and its community contributions for a project. It explains how to resume from a community contribution by cloning or downloading their repository and setting the skill library directory accordingly. It also provides information on how to contribute after running the learning process, resulting in a checkpoint directory with various files.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/skill_library/README.md\":0-33", + "content": "# Learned Skill Libraries\n## Ours\n* [skill_library/trial1](trial1)\n* [skill_library/trial2](trial2)\n* [skill_library/trial3](trial3)\n## Community Contributions\n* [daswer123/Voyager_checkpoint](https://github.com/daswer123/Voyager_checkpoint)\n* [swen128/Voyager_checkpoint](https://github.com/swen128/Voyager_checkpoint)\n* [DeveloperHarris/voyager_checkpoint](https://github.com/DeveloperHarris/voyager_checkpoint)\n### How to resume from a community contribution\nFirst, you need to clone or download their repo. Then, the resume is the same as using ours skill libraries. Just set `skill_library_dir=COMMUNITY_CKPT_DIR` where `COMMUNITY_CKPT_DIR` is the ckpt dir inside the folder you just downloaded.\n## How to Contribute\nAfter you run the learning process, you will see a checkpoint directory like:\n```\n.\n\u251c\u2500\u2500 action\n\u2502 \u2514\u2500\u2500 chest_memory.json\n\u251c\u2500\u2500 curriculum\n\u2502 \u251c\u2500\u2500 completed_tasks.json\n\u2502 \u251c\u2500\u2500 failed_tasks.json\n\u2502 \u251c\u2500\u2500 qa_cache.json\n\u2502 \u2514\u2500\u2500 vectordb\n\u251c\u2500\u2500 events\n\u2514\u2500\u2500 skill\n \u251c\u2500\u2500 code\n \u2502 \u251c\u2500\u2500 catchThreeFishWithCheck.js\n \u2502 \u251c\u2500\u2500 collectBamboo.js" + }, + { + "comment": "The code represents a skill library containing multiple text files, a JSON file, and a vector database. Only the `YOUR_CKPT_DIR/skill` directory is a learned skill library that can be shared with others by creating a pull request and adding the link to this page.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/skill_library/README.md\":34-43", + "content": " \u2502 \u251c\u2500\u2500 ...\n \u251c\u2500\u2500 description\n \u2502 \u251c\u2500\u2500 catchThreeFishWithCheck.txt\n \u2502 \u251c\u2500\u2500 collectBamboo.txt\n \u2502 \u2514\u2500\u2500 ...\n \u251c\u2500\u2500 skills.json\n \u2514\u2500\u2500 vectordb\n```\nOnly `YOUR_CKPT_DIR/skill` is a learned skill library, which you can share with others. Create a pull request and add your skill library link to this page." + } + ] +} \ No newline at end of file diff --git a/docs/doc/fb6f1194-267a-4455-bf8d-6e9a96bc0f1a.json b/docs/doc/fb6f1194-267a-4455-bf8d-6e9a96bc0f1a.json new file mode 100644 index 00000000..af3e7089 --- /dev/null +++ b/docs/doc/fb6f1194-267a-4455-bf8d-6e9a96bc0f1a.json @@ -0,0 +1,25 @@ +{ + "summary": "The code creates a MinecraftInstance class, initializes functions for Mineflayer process and log path, runs node command with script path and server port, handles user login and config options management.", + "details": [ + { + "comment": "The code defines a class \"MinecraftInstance\" with attributes such as client_id, redirect_url, secret_value, version, mineflayer, log_path, and mc_dir. It initializes a stop_mineflayer() function to stop the mineflayer process, gets the mc_command using get_mc_command(), and creates a SubprocessMonitor for the minecraft process with specified commands, name, ready_match, and log_path.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/env/minecraft_launcher.py\":0-40", + "content": "import os\nimport re\nimport minecraft_launcher_lib\nimport sys\nimport voyager.utils as U\nfrom .process_monitor import SubprocessMonitor\nclass MinecraftInstance:\n def __init__(\n self,\n client_id,\n redirect_url,\n secret_value,\n version,\n mineflayer,\n log_path=\"logs\",\n ):\n self.client_id = client_id\n self.redirect_url = redirect_url\n self.secret_value = secret_value\n self.version = version\n self.log_path = log_path\n self.mc_dir = minecraft_launcher_lib.utils.get_minecraft_directory()\n self.port = None\n def stop_mineflayer():\n print(\"Stopping mineflayer\")\n try:\n mineflayer.stop()\n except Exception as e:\n print(e)\n self.mc_command = self.get_mc_command()\n self.mc_process = SubprocessMonitor(\n commands=self.mc_command,\n name=\"minecraft\",\n ready_match=r\"Started serving on (\\d+)\",\n log_path=self.log_path," + }, + { + "comment": "Line 41-68:\n- Initializes a `SubprocessMonitor` for Mineflayer process\n- Sets the log path to \"../mineflayer\" and gets the absolute file path\n- Runs \"node\" command with the script path, server port as arguments\n- Defines ready match pattern as \"Server started on port (\\d+)\"\n- Checks if config.json exists", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/env/minecraft_launcher.py\":41-68", + "content": " callback=stop_mineflayer,\n callback_match=r\"\\[Server thread/INFO\\]: bot left the game\",\n finished_callback=stop_mineflayer,\n )\n def get_mineflayer_process(self, server_port):\n U.f_mkdir(self.log_path, \"../mineflayer\")\n file_path = os.path.abspath(os.path.dirname(__file__))\n return SubprocessMonitor(\n commands=[\n \"node\",\n U.f_join(file_path, \"mineflayer/index.js\"),\n str(server_port),\n ],\n name=\"mineflayer\",\n ready_match=r\"Server started on port (\\d+)\",\n log_path=U.f_join(self.log_path, \"mineflayer\"),\n )\n def get_mc_command(self):\n file_path = os.path.abspath(os.path.dirname(__file__))\n if not U.f_exists(file_path, \"config.json\"):\n (\n login_url,\n state,\n code_verifier,\n ) = minecraft_launcher_lib.microsoft_account.get_secure_login_data(\n self.client_id, self.redirect_url" + }, + { + "comment": "The code prompts the user to open a login URL in their browser, enter the redirected URL into the prompt, and then parses the authentication code from the entered URL. It validates the state and URL, then completes the login process by retrieving the username, UUID, and access token from the login data.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/env/minecraft_launcher.py\":69-99", + "content": " )\n print(\n f\"Please open {login_url} in your browser and copy the url you are redirected into the prompt below.\"\n )\n code_url = input()\n try:\n auth_code = (\n minecraft_launcher_lib.microsoft_account.parse_auth_code_url(\n code_url, state\n )\n )\n except AssertionError:\n print(\"States do not match!\")\n sys.exit(1)\n except KeyError:\n print(\"Url not valid\")\n sys.exit(1)\n login_data = minecraft_launcher_lib.microsoft_account.complete_login(\n self.client_id,\n self.secret_value,\n self.redirect_url,\n auth_code,\n code_verifier,\n )\n options = {\n \"username\": login_data[\"name\"],\n \"uuid\": login_data[\"id\"],\n \"token\": login_data[\"access_token\"]," + }, + { + "comment": "This code defines a class with methods to start, stop, and check the status of a Minecraft server. It loads or saves configuration options, retrieves the Minecraft command, and prints the port when the server is running.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/env/minecraft_launcher.py\":100-126", + "content": " }\n U.json_dump(options, file_path, \"config.json\")\n print(f\"Login success, save to {U.f_join(file_path, 'config.json')}\")\n options = U.json_load(file_path, \"config.json\")\n mc_command = minecraft_launcher_lib.command.get_minecraft_command(\n self.version, self.mc_dir, options\n )\n return mc_command\n def run(self):\n self.mc_process.run()\n pattern = r\"Started serving on (\\d+)\"\n match = re.search(pattern, self.mc_process.ready_line)\n if match:\n self.port = int(match.group(1))\n print(\"The mc server is listening on port\", self.port)\n else:\n raise RuntimeError(\"Port not found\")\n def stop(self):\n self.mc_process.stop()\n @property\n def is_running(self):\n return self.mc_process.is_running" + } + ] +} \ No newline at end of file diff --git a/docs/doc/feb1e907-3ace-4e84-8bb1-4c7fb10be9f5.json b/docs/doc/feb1e907-3ace-4e84-8bb1-4c7fb10be9f5.json new file mode 100644 index 00000000..f5784b84 --- /dev/null +++ b/docs/doc/feb1e907-3ace-4e84-8bb1-4c7fb10be9f5.json @@ -0,0 +1,20 @@ +{ + "summary": "The code allows the Mineflayer bot to move, change equipment, target blocks/entities using goals and functions. It demonstrates how to activate an item and use it on an entity in proper order for functionality.", + "details": [ + { + "comment": "This code contains various goals and functions that can be used with the Mineflayer bot for movement, equipment changes, and targeting specific blocks or entities. The `goto` function allows the bot to move towards a specified goal location, while other goals like `GoalNear`, `GoalXZ`, `GoalGetToBlock`, `GoalFollow`, `GoalPlaceBlock`, and `GoalLookAtBlock` provide more specific options for targeting and movement.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/control_primitives_context/mineflayer.js\":0-9", + "content": "await bot.pathfinder.goto(goal); // A very useful function. This function may change your main-hand equipment.\n// Following are some Goals you can use:\nnew GoalNear(x, y, z, range); // Move the bot to a block within the specified range of the specified block. `x`, `y`, `z`, and `range` are `number`\nnew GoalXZ(x, z); // Useful for long-range goals that don't have a specific Y level. `x` and `z` are `number`\nnew GoalGetToBlock(x, y, z); // Not get into the block, but get directly adjacent to it. Useful for fishing, farming, filling bucket, and beds. `x`, `y`, and `z` are `number`\nnew GoalFollow(entity, range); // Follow the specified entity within the specified range. `entity` is `Entity`, `range` is `number`\nnew GoalPlaceBlock(position, bot.world, {}); // Position the bot in order to place a block. `position` is `Vec3`\nnew GoalLookAtBlock(position, bot.world, {}); // Path into a position where a blockface of the block at position is visible. `position` is `Vec3`\n// These are other Mineflayer functions you can use:" + }, + { + "comment": "The code provides a bot object with various Minecraft-related functions. The 'bot.isABed()' checks if 'bedBlock' is a bed, while 'bot.blockAt()' retrieves the block at specified position. Other functions allow equipping items, consuming items, fishing, sleeping until sunrise, activating blocks, and looking at specific positions using the bot object.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/control_primitives_context/mineflayer.js\":10-19", + "content": "bot.isABed(bedBlock); // Return true if `bedBlock` is a bed\nbot.blockAt(position); // Return the block at `position`. `position` is `Vec3`\n// These are other Mineflayer async functions you can use:\nawait bot.equip(item, destination); // Equip the item in the specified destination. `item` is `Item`, `destination` can only be \"hand\", \"head\", \"torso\", \"legs\", \"feet\", \"off-hand\"\nawait bot.consume(); // Consume the item in the bot's hand. You must equip the item to consume first. Useful for eating food, drinking potions, etc.\nawait bot.fish(); // Let bot fish. Before calling this function, you must first get to a water block and then equip a fishing rod. The bot will automatically stop fishing when it catches a fish\nawait bot.sleep(bedBlock); // Sleep until sunrise. You must get to a bed block first\nawait bot.activateBlock(block); // This is the same as right-clicking a block in the game. Useful for buttons, doors, etc. You must get to the block first\nawait bot.lookAt(position); // Look at the spec" + }, + { + "comment": "The code snippet is from the Voyager project's \"mineflayer.js\" file, where it demonstrates how to activate an item (such as a bucket) and use it on an entity (e.g., filling the bucket with water). The sequence starts by activating the item using \"await bot.activateItem();\", followed by using the item on the entity with \"await bot.useOn(entity);\". It's crucial to follow this order - equip the item first and then use it on the entity for proper functionality in the Minecraft game.", + "location": "\"/media/root/Prima/works/Voyager/docs/src/voyager/control_primitives_context/mineflayer.js\":19-21", + "content": "ified position. You must go near the position before you look at it. To fill bucket with water, you must lookAt first. `position` is `Vec3`\nawait bot.activateItem(); // This is the same as right-clicking to use the item in the bot's hand. Useful for using buckets, etc. You must equip the item to activate first\nawait bot.useOn(entity); // This is the same as right-clicking an entity in the game. Useful for shearing sheep, equipping harnesses, etc. You must get to the entity first" + } + ] +} \ No newline at end of file diff --git a/docs/github-markdown.css b/docs/github-markdown.css new file mode 100755 index 00000000..96a4f29e --- /dev/null +++ b/docs/github-markdown.css @@ -0,0 +1,1197 @@ +@media (prefers-color-scheme: dark) { + + .markdown-body, + [data-theme="dark"] { + /*dark*/ + color-scheme: dark; + --color-prettylights-syntax-comment: #8b949e; + --color-prettylights-syntax-constant: #79c0ff; + --color-prettylights-syntax-entity: #d2a8ff; + --color-prettylights-syntax-storage-modifier-import: #c9d1d9; + --color-prettylights-syntax-entity-tag: #7ee787; + --color-prettylights-syntax-keyword: #ff7b72; + --color-prettylights-syntax-string: #a5d6ff; + --color-prettylights-syntax-variable: #ffa657; + --color-prettylights-syntax-brackethighlighter-unmatched: #f85149; + --color-prettylights-syntax-invalid-illegal-text: #f0f6fc; + --color-prettylights-syntax-invalid-illegal-bg: #8e1519; + --color-prettylights-syntax-carriage-return-text: #f0f6fc; + --color-prettylights-syntax-carriage-return-bg: #b62324; + --color-prettylights-syntax-string-regexp: #7ee787; + --color-prettylights-syntax-markup-list: #f2cc60; + --color-prettylights-syntax-markup-heading: #1f6feb; + --color-prettylights-syntax-markup-italic: #c9d1d9; + --color-prettylights-syntax-markup-bold: #c9d1d9; + --color-prettylights-syntax-markup-deleted-text: #ffdcd7; + --color-prettylights-syntax-markup-deleted-bg: #67060c; + --color-prettylights-syntax-markup-inserted-text: #aff5b4; + --color-prettylights-syntax-markup-inserted-bg: #033a16; + --color-prettylights-syntax-markup-changed-text: #ffdfb6; + --color-prettylights-syntax-markup-changed-bg: #5a1e02; + --color-prettylights-syntax-markup-ignored-text: #c9d1d9; + --color-prettylights-syntax-markup-ignored-bg: #1158c7; + --color-prettylights-syntax-meta-diff-range: #d2a8ff; + --color-prettylights-syntax-brackethighlighter-angle: #8b949e; + --color-prettylights-syntax-sublimelinter-gutter-mark: #484f58; + --color-prettylights-syntax-constant-other-reference-link: #a5d6ff; + --color-fg-default: #e6edf3; + --color-fg-muted: #848d97; + --color-fg-subtle: #6e7681; + --color-canvas-default: #0d1117; + --color-canvas-subtle: #161b22; + --color-border-default: #30363d; + --color-border-muted: #21262d; + --color-neutral-muted: rgba(110, 118, 129, 0.4); + --color-accent-fg: #2f81f7; + --color-accent-emphasis: #1f6feb; + --color-success-fg: #3fb950; + --color-success-emphasis: #238636; + --color-attention-fg: #d29922; + --color-attention-emphasis: #9e6a03; + --color-attention-subtle: rgba(187, 128, 9, 0.15); + --color-danger-fg: #f85149; + --color-danger-emphasis: #da3633; + --color-done-fg: #a371f7; + --color-done-emphasis: #8957e5; + } +} + +@media (prefers-color-scheme: light) { + + .markdown-body, + [data-theme="light"] { + /*light*/ + color-scheme: light; + --color-prettylights-syntax-comment: #57606a; + --color-prettylights-syntax-constant: #0550ae; + --color-prettylights-syntax-entity: #6639ba; + --color-prettylights-syntax-storage-modifier-import: #24292f; + --color-prettylights-syntax-entity-tag: #116329; + --color-prettylights-syntax-keyword: #cf222e; + --color-prettylights-syntax-string: #0a3069; + --color-prettylights-syntax-variable: #953800; + --color-prettylights-syntax-brackethighlighter-unmatched: #82071e; + --color-prettylights-syntax-invalid-illegal-text: #f6f8fa; + --color-prettylights-syntax-invalid-illegal-bg: #82071e; + --color-prettylights-syntax-carriage-return-text: #f6f8fa; + --color-prettylights-syntax-carriage-return-bg: #cf222e; + --color-prettylights-syntax-string-regexp: #116329; + --color-prettylights-syntax-markup-list: #3b2300; + --color-prettylights-syntax-markup-heading: #0550ae; + --color-prettylights-syntax-markup-italic: #24292f; + --color-prettylights-syntax-markup-bold: #24292f; + --color-prettylights-syntax-markup-deleted-text: #82071e; + --color-prettylights-syntax-markup-deleted-bg: #ffebe9; + --color-prettylights-syntax-markup-inserted-text: #116329; + --color-prettylights-syntax-markup-inserted-bg: #dafbe1; + --color-prettylights-syntax-markup-changed-text: #953800; + --color-prettylights-syntax-markup-changed-bg: #ffd8b5; + --color-prettylights-syntax-markup-ignored-text: #eaeef2; + --color-prettylights-syntax-markup-ignored-bg: #0550ae; + --color-prettylights-syntax-meta-diff-range: #8250df; + --color-prettylights-syntax-brackethighlighter-angle: #57606a; + --color-prettylights-syntax-sublimelinter-gutter-mark: #8c959f; + --color-prettylights-syntax-constant-other-reference-link: #0a3069; + --color-fg-default: #1F2328; + --color-fg-muted: #656d76; + --color-fg-subtle: #6e7781; + --color-canvas-default: #ffffff; + --color-canvas-subtle: #f6f8fa; + --color-border-default: #d0d7de; + --color-border-muted: hsla(210, 18%, 87%, 1); + --color-neutral-muted: rgba(175, 184, 193, 0.2); + --color-accent-fg: #0969da; + --color-accent-emphasis: #0969da; + --color-success-fg: #1a7f37; + --color-success-emphasis: #1f883d; + --color-attention-fg: #9a6700; + --color-attention-emphasis: #9a6700; + --color-attention-subtle: #fff8c5; + --color-danger-fg: #d1242f; + --color-danger-emphasis: #cf222e; + --color-done-fg: #8250df; + --color-done-emphasis: #8250df; + } +} + +.markdown-body { + -ms-text-size-adjust: 100%; + -webkit-text-size-adjust: 100%; + margin: 0; + color: var(--color-fg-default); + background-color: var(--color-canvas-default); + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; + font-size: 16px; + line-height: 1.5; + word-wrap: break-word; +} + +.markdown-body .octicon { + display: inline-block; + fill: currentColor; + vertical-align: text-bottom; +} + +.markdown-body h1:hover .anchor .octicon-link:before, +.markdown-body h2:hover .anchor .octicon-link:before, +.markdown-body h3:hover .anchor .octicon-link:before, +.markdown-body h4:hover .anchor .octicon-link:before, +.markdown-body h5:hover .anchor .octicon-link:before, +.markdown-body h6:hover .anchor .octicon-link:before { + width: 16px; + height: 16px; + content: ' '; + display: inline-block; + background-color: currentColor; + -webkit-mask-image: url("data:image/svg+xml,"); + mask-image: url("data:image/svg+xml,"); +} + +.markdown-body details, +.markdown-body figcaption, +.markdown-body figure { + display: block; +} + +.markdown-body summary { + display: list-item; +} + +.markdown-body [hidden] { + display: none !important; +} + +.markdown-body a { + background-color: transparent; + color: var(--color-accent-fg); + text-decoration: none; +} + +.markdown-body abbr[title] { + border-bottom: none; + -webkit-text-decoration: underline dotted; + text-decoration: underline dotted; +} + +.markdown-body b, +.markdown-body strong { + font-weight: var(--base-text-weight-semibold, 600); +} + +.markdown-body dfn { + font-style: italic; +} + +.markdown-body h1 { + margin: .67em 0; + font-weight: var(--base-text-weight-semibold, 600); + padding-bottom: .3em; + font-size: 2em; + border-bottom: 1px solid var(--color-border-muted); +} + +.markdown-body mark { + background-color: var(--color-attention-subtle); + color: var(--color-fg-default); +} + +.markdown-body small { + font-size: 90%; +} + +.markdown-body sub, +.markdown-body sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +.markdown-body sub { + bottom: -0.25em; +} + +.markdown-body sup { + top: -0.5em; +} + +.markdown-body img { + border-style: none; + max-width: 100%; + box-sizing: content-box; + background-color: var(--color-canvas-default); +} + +.markdown-body code, +.markdown-body kbd, +.markdown-body pre, +.markdown-body samp { + font-family: monospace; + font-size: 1em; +} + +.markdown-body figure { + margin: 1em 40px; +} + +.markdown-body hr { + box-sizing: content-box; + overflow: hidden; + background: transparent; + border-bottom: 1px solid var(--color-border-muted); + height: .25em; + padding: 0; + margin: 24px 0; + background-color: var(--color-border-default); + border: 0; +} + +.markdown-body input { + font: inherit; + margin: 0; + overflow: visible; + font-family: inherit; + font-size: inherit; + line-height: inherit; +} + +.markdown-body [type=button], +.markdown-body [type=reset], +.markdown-body [type=submit] { + -webkit-appearance: button; + appearance: button; +} + +.markdown-body [type=checkbox], +.markdown-body [type=radio] { + box-sizing: border-box; + padding: 0; +} + +.markdown-body [type=number]::-webkit-inner-spin-button, +.markdown-body [type=number]::-webkit-outer-spin-button { + height: auto; +} + +.markdown-body [type=search]::-webkit-search-cancel-button, +.markdown-body [type=search]::-webkit-search-decoration { + -webkit-appearance: none; + appearance: none; +} + +.markdown-body ::-webkit-input-placeholder { + color: inherit; + opacity: .54; +} + +.markdown-body ::-webkit-file-upload-button { + -webkit-appearance: button; + appearance: button; + font: inherit; +} + +.markdown-body a:hover { + text-decoration: underline; +} + +.markdown-body ::placeholder { + color: var(--color-fg-subtle); + opacity: 1; +} + +.markdown-body hr::before { + display: table; + content: ""; +} + +.markdown-body hr::after { + display: table; + clear: both; + content: ""; +} + +.markdown-body table { + border-spacing: 0; + border-collapse: collapse; + display: block; + width: max-content; + max-width: 100%; + overflow: auto; +} + +.markdown-body td, +.markdown-body th { + padding: 0; +} + +.markdown-body details summary { + cursor: pointer; +} + +.markdown-body details:not([open])>*:not(summary) { + display: none !important; +} + +.markdown-body a:focus, +.markdown-body [role=button]:focus, +.markdown-body input[type=radio]:focus, +.markdown-body input[type=checkbox]:focus { + outline: 2px solid var(--color-accent-fg); + outline-offset: -2px; + box-shadow: none; +} + +.markdown-body a:focus:not(:focus-visible), +.markdown-body [role=button]:focus:not(:focus-visible), +.markdown-body input[type=radio]:focus:not(:focus-visible), +.markdown-body input[type=checkbox]:focus:not(:focus-visible) { + outline: solid 1px transparent; +} + +.markdown-body a:focus-visible, +.markdown-body [role=button]:focus-visible, +.markdown-body input[type=radio]:focus-visible, +.markdown-body input[type=checkbox]:focus-visible { + outline: 2px solid var(--color-accent-fg); + outline-offset: -2px; + box-shadow: none; +} + +.markdown-body a:not([class]):focus, +.markdown-body a:not([class]):focus-visible, +.markdown-body input[type=radio]:focus, +.markdown-body input[type=radio]:focus-visible, +.markdown-body input[type=checkbox]:focus, +.markdown-body input[type=checkbox]:focus-visible { + outline-offset: 0; +} + +.markdown-body kbd { + display: inline-block; + padding: 3px 5px; + font: 11px ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace; + line-height: 10px; + color: var(--color-fg-default); + vertical-align: middle; + background-color: var(--color-canvas-subtle); + border: solid 1px var(--color-neutral-muted); + border-bottom-color: var(--color-neutral-muted); + border-radius: 6px; + box-shadow: inset 0 -1px 0 var(--color-neutral-muted); +} + +.markdown-body h1, +.markdown-body h2, +.markdown-body h3, +.markdown-body h4, +.markdown-body h5, +.markdown-body h6 { + margin-top: 24px; + margin-bottom: 16px; + font-weight: var(--base-text-weight-semibold, 600); + line-height: 1.25; +} + +.markdown-body h2 { + font-weight: var(--base-text-weight-semibold, 600); + padding-bottom: .3em; + font-size: 1.5em; + border-bottom: 1px solid var(--color-border-muted); +} + +.markdown-body h3 { + font-weight: var(--base-text-weight-semibold, 600); + font-size: 1.25em; +} + +.markdown-body h4 { + font-weight: var(--base-text-weight-semibold, 600); + font-size: 1em; +} + +.markdown-body h5 { + font-weight: var(--base-text-weight-semibold, 600); + font-size: .875em; +} + +.markdown-body h6 { + font-weight: var(--base-text-weight-semibold, 600); + font-size: .85em; + color: var(--color-fg-muted); +} + +.markdown-body p { + margin-top: 0; + margin-bottom: 10px; +} + +.markdown-body blockquote { + margin: 0; + padding: 0 1em; + color: var(--color-fg-muted); + border-left: .25em solid var(--color-border-default); +} + +.markdown-body ul, +.markdown-body ol { + margin-top: 0; + margin-bottom: 0; + padding-left: 2em; +} + +.markdown-body ol ol, +.markdown-body ul ol { + list-style-type: lower-roman; +} + +.markdown-body ul ul ol, +.markdown-body ul ol ol, +.markdown-body ol ul ol, +.markdown-body ol ol ol { + list-style-type: lower-alpha; +} + +.markdown-body dd { + margin-left: 0; +} + +.markdown-body tt, +.markdown-body code, +.markdown-body samp { + font-family: ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace; + font-size: 12px; +} + +.markdown-body pre { + margin-top: 0; + margin-bottom: 0; + font-family: ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace; + font-size: 12px; + word-wrap: normal; +} + +.markdown-body .octicon { + display: inline-block; + overflow: visible !important; + vertical-align: text-bottom; + fill: currentColor; +} + +.markdown-body input::-webkit-outer-spin-button, +.markdown-body input::-webkit-inner-spin-button { + margin: 0; + -webkit-appearance: none; + appearance: none; +} + +.markdown-body .mr-2 { + margin-right: var(--base-size-8, 8px) !important; +} + +.markdown-body::before { + display: table; + content: ""; +} + +.markdown-body::after { + display: table; + clear: both; + content: ""; +} + +.markdown-body>*:first-child { + margin-top: 0 !important; +} + +.markdown-body>*:last-child { + margin-bottom: 0 !important; +} + +.markdown-body a:not([href]) { + color: inherit; + text-decoration: none; +} + +.markdown-body .absent { + color: var(--color-danger-fg); +} + +.markdown-body .anchor { + float: left; + padding-right: 4px; + margin-left: -20px; + line-height: 1; +} + +.markdown-body .anchor:focus { + outline: none; +} + +.markdown-body p, +.markdown-body blockquote, +.markdown-body ul, +.markdown-body ol, +.markdown-body dl, +.markdown-body table, +.markdown-body pre, +.markdown-body details { + margin-top: 0; + margin-bottom: 16px; +} + +.markdown-body blockquote>:first-child { + margin-top: 0; +} + +.markdown-body blockquote>:last-child { + margin-bottom: 0; +} + +.markdown-body h1 .octicon-link, +.markdown-body h2 .octicon-link, +.markdown-body h3 .octicon-link, +.markdown-body h4 .octicon-link, +.markdown-body h5 .octicon-link, +.markdown-body h6 .octicon-link { + color: var(--color-fg-default); + vertical-align: middle; + visibility: hidden; +} + +.markdown-body h1:hover .anchor, +.markdown-body h2:hover .anchor, +.markdown-body h3:hover .anchor, +.markdown-body h4:hover .anchor, +.markdown-body h5:hover .anchor, +.markdown-body h6:hover .anchor { + text-decoration: none; +} + +.markdown-body h1:hover .anchor .octicon-link, +.markdown-body h2:hover .anchor .octicon-link, +.markdown-body h3:hover .anchor .octicon-link, +.markdown-body h4:hover .anchor .octicon-link, +.markdown-body h5:hover .anchor .octicon-link, +.markdown-body h6:hover .anchor .octicon-link { + visibility: visible; +} + +.markdown-body h1 tt, +.markdown-body h1 code, +.markdown-body h2 tt, +.markdown-body h2 code, +.markdown-body h3 tt, +.markdown-body h3 code, +.markdown-body h4 tt, +.markdown-body h4 code, +.markdown-body h5 tt, +.markdown-body h5 code, +.markdown-body h6 tt, +.markdown-body h6 code { + padding: 0 .2em; + font-size: inherit; +} + +.markdown-body summary h1, +.markdown-body summary h2, +.markdown-body summary h3, +.markdown-body summary h4, +.markdown-body summary h5, +.markdown-body summary h6 { + display: inline-block; +} + +.markdown-body summary h1 .anchor, +.markdown-body summary h2 .anchor, +.markdown-body summary h3 .anchor, +.markdown-body summary h4 .anchor, +.markdown-body summary h5 .anchor, +.markdown-body summary h6 .anchor { + margin-left: -40px; +} + +.markdown-body summary h1, +.markdown-body summary h2 { + padding-bottom: 0; + border-bottom: 0; +} + +.markdown-body ul.no-list, +.markdown-body ol.no-list { + padding: 0; + list-style-type: none; +} + +.markdown-body ol[type="a s"] { + list-style-type: lower-alpha; +} + +.markdown-body ol[type="A s"] { + list-style-type: upper-alpha; +} + +.markdown-body ol[type="i s"] { + list-style-type: lower-roman; +} + +.markdown-body ol[type="I s"] { + list-style-type: upper-roman; +} + +.markdown-body ol[type="1"] { + list-style-type: decimal; +} + +.markdown-body div>ol:not([type]) { + list-style-type: decimal; +} + +.markdown-body ul ul, +.markdown-body ul ol, +.markdown-body ol ol, +.markdown-body ol ul { + margin-top: 0; + margin-bottom: 0; +} + +.markdown-body li>p { + margin-top: 16px; +} + +.markdown-body li+li { + margin-top: .25em; +} + +.markdown-body dl { + padding: 0; +} + +.markdown-body dl dt { + padding: 0; + margin-top: 16px; + font-size: 1em; + font-style: italic; + font-weight: var(--base-text-weight-semibold, 600); +} + +.markdown-body dl dd { + padding: 0 16px; + margin-bottom: 16px; +} + +.markdown-body table th { + font-weight: var(--base-text-weight-semibold, 600); +} + +.markdown-body table th, +.markdown-body table td { + padding: 6px 13px; + border: 1px solid var(--color-border-default); +} + +.markdown-body table td>:last-child { + margin-bottom: 0; +} + +.markdown-body table tr { + background-color: var(--color-canvas-default); + border-top: 1px solid var(--color-border-muted); +} + +.markdown-body table tr:nth-child(2n) { + background-color: var(--color-canvas-subtle); +} + +.markdown-body table img { + background-color: transparent; +} + +.markdown-body img[align=right] { + padding-left: 20px; +} + +.markdown-body img[align=left] { + padding-right: 20px; +} + +.markdown-body .emoji { + max-width: none; + vertical-align: text-top; + background-color: transparent; +} + +.markdown-body span.frame { + display: block; + overflow: hidden; +} + +.markdown-body span.frame>span { + display: block; + float: left; + width: auto; + padding: 7px; + margin: 13px 0 0; + overflow: hidden; + border: 1px solid var(--color-border-default); +} + +.markdown-body span.frame span img { + display: block; + float: left; +} + +.markdown-body span.frame span span { + display: block; + padding: 5px 0 0; + clear: both; + color: var(--color-fg-default); +} + +.markdown-body span.align-center { + display: block; + overflow: hidden; + clear: both; +} + +.markdown-body span.align-center>span { + display: block; + margin: 13px auto 0; + overflow: hidden; + text-align: center; +} + +.markdown-body span.align-center span img { + margin: 0 auto; + text-align: center; +} + +.markdown-body span.align-right { + display: block; + overflow: hidden; + clear: both; +} + +.markdown-body span.align-right>span { + display: block; + margin: 13px 0 0; + overflow: hidden; + text-align: right; +} + +.markdown-body span.align-right span img { + margin: 0; + text-align: right; +} + +.markdown-body span.float-left { + display: block; + float: left; + margin-right: 13px; + overflow: hidden; +} + +.markdown-body span.float-left span { + margin: 13px 0 0; +} + +.markdown-body span.float-right { + display: block; + float: right; + margin-left: 13px; + overflow: hidden; +} + +.markdown-body span.float-right>span { + display: block; + margin: 13px auto 0; + overflow: hidden; + text-align: right; +} + +.markdown-body code, +.markdown-body tt { + padding: .2em .4em; + margin: 0; + font-size: 85%; + white-space: break-spaces; + background-color: var(--color-neutral-muted); + border-radius: 6px; +} + +.markdown-body code br, +.markdown-body tt br { + display: none; +} + +.markdown-body del code { + text-decoration: inherit; +} + +.markdown-body samp { + font-size: 85%; +} + +.markdown-body pre code { + font-size: 100%; +} + +.markdown-body pre>code { + padding: 0; + margin: 0; + word-break: normal; + white-space: pre; + background: transparent; + border: 0; +} + +.markdown-body .highlight { + margin-bottom: 16px; +} + +.markdown-body .highlight pre { + margin-bottom: 0; + word-break: normal; +} + +.markdown-body .highlight pre, +.markdown-body pre { + padding: 16px; + overflow: auto; + font-size: 85%; + line-height: 1.45; + color: var(--color-fg-default); + background-color: var(--color-canvas-subtle); + border-radius: 6px; +} + +.markdown-body pre code, +.markdown-body pre tt { + display: inline; + max-width: auto; + padding: 0; + margin: 0; + overflow: visible; + line-height: inherit; + word-wrap: normal; + background-color: transparent; + border: 0; +} + +.markdown-body .csv-data td, +.markdown-body .csv-data th { + padding: 5px; + overflow: hidden; + font-size: 12px; + line-height: 1; + text-align: left; + white-space: nowrap; +} + +.markdown-body .csv-data .blob-num { + padding: 10px 8px 9px; + text-align: right; + background: var(--color-canvas-default); + border: 0; +} + +.markdown-body .csv-data tr { + border-top: 0; +} + +.markdown-body .csv-data th { + font-weight: var(--base-text-weight-semibold, 600); + background: var(--color-canvas-subtle); + border-top: 0; +} + +.markdown-body [data-footnote-ref]::before { + content: "["; +} + +.markdown-body [data-footnote-ref]::after { + content: "]"; +} + +.markdown-body .footnotes { + font-size: 12px; + color: var(--color-fg-muted); + border-top: 1px solid var(--color-border-default); +} + +.markdown-body .footnotes ol { + padding-left: 16px; +} + +.markdown-body .footnotes ol ul { + display: inline-block; + padding-left: 16px; + margin-top: 16px; +} + +.markdown-body .footnotes li { + position: relative; +} + +.markdown-body .footnotes li:target::before { + position: absolute; + top: -8px; + right: -8px; + bottom: -8px; + left: -24px; + pointer-events: none; + content: ""; + border: 2px solid var(--color-accent-emphasis); + border-radius: 6px; +} + +.markdown-body .footnotes li:target { + color: var(--color-fg-default); +} + +.markdown-body .footnotes .data-footnote-backref g-emoji { + font-family: monospace; +} + +.markdown-body .pl-c { + color: var(--color-prettylights-syntax-comment); +} + +.markdown-body .pl-c1, +.markdown-body .pl-s .pl-v { + color: var(--color-prettylights-syntax-constant); +} + +.markdown-body .pl-e, +.markdown-body .pl-en { + color: var(--color-prettylights-syntax-entity); +} + +.markdown-body .pl-smi, +.markdown-body .pl-s .pl-s1 { + color: var(--color-prettylights-syntax-storage-modifier-import); +} + +.markdown-body .pl-ent { + color: var(--color-prettylights-syntax-entity-tag); +} + +.markdown-body .pl-k { + color: var(--color-prettylights-syntax-keyword); +} + +.markdown-body .pl-s, +.markdown-body .pl-pds, +.markdown-body .pl-s .pl-pse .pl-s1, +.markdown-body .pl-sr, +.markdown-body .pl-sr .pl-cce, +.markdown-body .pl-sr .pl-sre, +.markdown-body .pl-sr .pl-sra { + color: var(--color-prettylights-syntax-string); +} + +.markdown-body .pl-v, +.markdown-body .pl-smw { + color: var(--color-prettylights-syntax-variable); +} + +.markdown-body .pl-bu { + color: var(--color-prettylights-syntax-brackethighlighter-unmatched); +} + +.markdown-body .pl-ii { + color: var(--color-prettylights-syntax-invalid-illegal-text); + background-color: var(--color-prettylights-syntax-invalid-illegal-bg); +} + +.markdown-body .pl-c2 { + color: var(--color-prettylights-syntax-carriage-return-text); + background-color: var(--color-prettylights-syntax-carriage-return-bg); +} + +.markdown-body .pl-sr .pl-cce { + font-weight: bold; + color: var(--color-prettylights-syntax-string-regexp); +} + +.markdown-body .pl-ml { + color: var(--color-prettylights-syntax-markup-list); +} + +.markdown-body .pl-mh, +.markdown-body .pl-mh .pl-en, +.markdown-body .pl-ms { + font-weight: bold; + color: var(--color-prettylights-syntax-markup-heading); +} + +.markdown-body .pl-mi { + font-style: italic; + color: var(--color-prettylights-syntax-markup-italic); +} + +.markdown-body .pl-mb { + font-weight: bold; + color: var(--color-prettylights-syntax-markup-bold); +} + +.markdown-body .pl-md { + color: var(--color-prettylights-syntax-markup-deleted-text); + background-color: var(--color-prettylights-syntax-markup-deleted-bg); +} + +.markdown-body .pl-mi1 { + color: var(--color-prettylights-syntax-markup-inserted-text); + background-color: var(--color-prettylights-syntax-markup-inserted-bg); +} + +.markdown-body .pl-mc { + color: var(--color-prettylights-syntax-markup-changed-text); + background-color: var(--color-prettylights-syntax-markup-changed-bg); +} + +.markdown-body .pl-mi2 { + color: var(--color-prettylights-syntax-markup-ignored-text); + background-color: var(--color-prettylights-syntax-markup-ignored-bg); +} + +.markdown-body .pl-mdr { + font-weight: bold; + color: var(--color-prettylights-syntax-meta-diff-range); +} + +.markdown-body .pl-ba { + color: var(--color-prettylights-syntax-brackethighlighter-angle); +} + +.markdown-body .pl-sg { + color: var(--color-prettylights-syntax-sublimelinter-gutter-mark); +} + +.markdown-body .pl-corl { + text-decoration: underline; + color: var(--color-prettylights-syntax-constant-other-reference-link); +} + +.markdown-body g-emoji { + display: inline-block; + min-width: 1ch; + font-family: "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; + font-size: 1em; + font-style: normal !important; + font-weight: var(--base-text-weight-normal, 400); + line-height: 1; + vertical-align: -0.075em; +} + +.markdown-body g-emoji img { + width: 1em; + height: 1em; +} + +.markdown-body .task-list-item { + list-style-type: none; +} + +.markdown-body .task-list-item label { + font-weight: var(--base-text-weight-normal, 400); +} + +.markdown-body .task-list-item.enabled label { + cursor: pointer; +} + +.markdown-body .task-list-item+.task-list-item { + margin-top: 4px; +} + +.markdown-body .task-list-item .handle { + display: none; +} + +.markdown-body .task-list-item-checkbox { + margin: 0 .2em .25em -1.4em; + vertical-align: middle; +} + +.markdown-body .contains-task-list:dir(rtl) .task-list-item-checkbox { + margin: 0 -1.6em .25em .2em; +} + +.markdown-body .contains-task-list { + position: relative; +} + +.markdown-body .contains-task-list:hover .task-list-item-convert-container, +.markdown-body .contains-task-list:focus-within .task-list-item-convert-container { + display: block; + width: auto; + height: 24px; + overflow: visible; + clip: auto; +} + +.markdown-body ::-webkit-calendar-picker-indicator { + filter: invert(50%); +} + +.markdown-body .markdown-alert { + padding: var(--base-size-8) var(--base-size-16); + margin-bottom: 16px; + color: inherit; + border-left: .25em solid var(--color-border-default); +} + +.markdown-body .markdown-alert>:first-child { + margin-top: 0; +} + +.markdown-body .markdown-alert>:last-child { + margin-bottom: 0; +} + +.markdown-body .markdown-alert .markdown-alert-title { + display: flex; + font-weight: var(--base-text-weight-medium, 500); + align-items: center; + line-height: 1; +} + +.markdown-body .markdown-alert.markdown-alert-note { + border-left-color: var(--color-accent-emphasis); +} + +.markdown-body .markdown-alert.markdown-alert-note .markdown-alert-title { + color: var(--color-accent-fg); +} + +.markdown-body .markdown-alert.markdown-alert-important { + border-left-color: var(--color-done-emphasis); +} + +.markdown-body .markdown-alert.markdown-alert-important .markdown-alert-title { + color: var(--color-done-fg); +} + +.markdown-body .markdown-alert.markdown-alert-warning { + border-left-color: var(--color-attention-emphasis); +} + +.markdown-body .markdown-alert.markdown-alert-warning .markdown-alert-title { + color: var(--color-attention-fg); +} + +.markdown-body .markdown-alert.markdown-alert-tip { + border-left-color: var(--color-success-emphasis); +} + +.markdown-body .markdown-alert.markdown-alert-tip .markdown-alert-title { + color: var(--color-success-fg); +} + +.markdown-body .markdown-alert.markdown-alert-caution { + border-left-color: var(--color-danger-emphasis); +} + +.markdown-body .markdown-alert.markdown-alert-caution .markdown-alert-title { + color: var(--color-danger-fg); +} \ No newline at end of file diff --git a/docs/index.html b/docs/index.html new file mode 100755 index 00000000..d1154b4d --- /dev/null +++ b/docs/index.html @@ -0,0 +1,1250 @@ + + + + + + + + + + Search Code By Comment + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+ + +
+

+
+ Document index of: +
+
+ + + +
+

+
+ + +
+
+ + + + + + + \ No newline at end of file diff --git a/docs/metadata.json b/docs/metadata.json new file mode 100644 index 00000000..b40d5404 --- /dev/null +++ b/docs/metadata.json @@ -0,0 +1,270 @@ +{ + "url": { + "full": "https://github.com/MineDojo/Voyager", + "partial": "MineDojo/Voyager" + }, + "file_mapping": { + "0": { + "filepath": "/FAQ.md", + "entry_id": 0, + "language_id": "markdown" + }, + "1": { + "filepath": "/README.md", + "entry_id": 12, + "language_id": "markdown" + }, + "2": { + "filepath": "/installation/fabric_mods_install.md", + "entry_id": 28, + "language_id": "markdown" + }, + "3": { + "filepath": "/installation/minecraft_instance_install.md", + "entry_id": 36, + "language_id": "markdown" + }, + "4": { + "filepath": "/requirements.txt", + "entry_id": 44, + "language_id": "plain-text" + }, + "5": { + "filepath": "/setup.py", + "entry_id": 48, + "language_id": "python" + }, + "6": { + "filepath": "/skill_library/README.md", + "entry_id": 54, + "language_id": "markdown" + }, + "7": { + "filepath": "/voyager/__init__.py", + "entry_id": 60, + "language_id": "python" + }, + "8": { + "filepath": "/voyager/agents/__init__.py", + "entry_id": 64, + "language_id": "python" + }, + "9": { + "filepath": "/voyager/agents/action.py", + "entry_id": 68, + "language_id": "python" + }, + "10": { + "filepath": "/voyager/agents/critic.py", + "entry_id": 92, + "language_id": "python" + }, + "11": { + "filepath": "/voyager/agents/curriculum.py", + "entry_id": 104, + "language_id": "python" + }, + "12": { + "filepath": "/voyager/agents/skill.py", + "entry_id": 146, + "language_id": "python" + }, + "13": { + "filepath": "/voyager/control_primitives/__init__.py", + "entry_id": 158, + "language_id": "python" + }, + "14": { + "filepath": "/voyager/control_primitives/craftHelper.js", + "entry_id": 162, + "language_id": "javascript" + }, + "15": { + "filepath": "/voyager/control_primitives/craftItem.js", + "entry_id": 170, + "language_id": "javascript" + }, + "16": { + "filepath": "/voyager/control_primitives/exploreUntil.js", + "entry_id": 176, + "language_id": "javascript" + }, + "17": { + "filepath": "/voyager/control_primitives/givePlacedItemBack.js", + "entry_id": 184, + "language_id": "javascript" + }, + "18": { + "filepath": "/voyager/control_primitives/killMob.js", + "entry_id": 190, + "language_id": "javascript" + }, + "19": { + "filepath": "/voyager/control_primitives/mineBlock.js", + "entry_id": 196, + "language_id": "javascript" + }, + "20": { + "filepath": "/voyager/control_primitives/placeItem.js", + "entry_id": 202, + "language_id": "javascript" + }, + "21": { + "filepath": "/voyager/control_primitives/shoot.js", + "entry_id": 210, + "language_id": "javascript" + }, + "22": { + "filepath": "/voyager/control_primitives/smeltItem.js", + "entry_id": 214, + "language_id": "javascript" + }, + "23": { + "filepath": "/voyager/control_primitives/useChest.js", + "entry_id": 222, + "language_id": "javascript" + }, + "24": { + "filepath": "/voyager/control_primitives/waitForMobRemoved.js", + "entry_id": 232, + "language_id": "javascript" + }, + "25": { + "filepath": "/voyager/control_primitives_context/__init__.py", + "entry_id": 240, + "language_id": "python" + }, + "26": { + "filepath": "/voyager/control_primitives_context/craftItem.js", + "entry_id": 244, + "language_id": "javascript" + }, + "27": { + "filepath": "/voyager/control_primitives_context/exploreUntil.js", + "entry_id": 248, + "language_id": "javascript" + }, + "28": { + "filepath": "/voyager/control_primitives_context/killMob.js", + "entry_id": 254, + "language_id": "javascript" + }, + "29": { + "filepath": "/voyager/control_primitives_context/mineBlock.js", + "entry_id": 258, + "language_id": "javascript" + }, + "30": { + "filepath": "/voyager/control_primitives_context/mineflayer.js", + "entry_id": 262, + "language_id": "javascript" + }, + "31": { + "filepath": "/voyager/control_primitives_context/placeItem.js", + "entry_id": 270, + "language_id": "javascript" + }, + "32": { + "filepath": "/voyager/control_primitives_context/smeltItem.js", + "entry_id": 276, + "language_id": "javascript" + }, + "33": { + "filepath": "/voyager/control_primitives_context/useChest.js", + "entry_id": 280, + "language_id": "javascript" + }, + "34": { + "filepath": "/voyager/env/__init__.py", + "entry_id": 286, + "language_id": "python" + }, + "35": { + "filepath": "/voyager/env/bridge.py", + "entry_id": 290, + "language_id": "python" + }, + "36": { + "filepath": "/voyager/env/minecraft_launcher.py", + "entry_id": 304, + "language_id": "python" + }, + "37": { + "filepath": "/voyager/env/process_monitor.py", + "entry_id": 314, + "language_id": "python" + }, + "38": { + "filepath": "/voyager/prompts/__init__.py", + "entry_id": 322, + "language_id": "python" + }, + "39": { + "filepath": "/voyager/prompts/action_response_format.txt", + "entry_id": 326, + "language_id": "plain-text" + }, + "40": { + "filepath": "/voyager/prompts/action_template.txt", + "entry_id": 330, + "language_id": "plain-text" + }, + "41": { + "filepath": "/voyager/prompts/critic.txt", + "entry_id": 338, + "language_id": "plain-text" + }, + "42": { + "filepath": "/voyager/prompts/curriculum.txt", + "entry_id": 348, + "language_id": "plain-text" + }, + "43": { + "filepath": "/voyager/prompts/curriculum_qa_step1_ask_questions.txt", + "entry_id": 356, + "language_id": "plain-text" + }, + "44": { + "filepath": "/voyager/prompts/curriculum_qa_step2_answer_questions.txt", + "entry_id": 366, + "language_id": "plain-text" + }, + "45": { + "filepath": "/voyager/prompts/curriculum_task_decomposition.txt", + "entry_id": 370, + "language_id": "plain-text" + }, + "46": { + "filepath": "/voyager/prompts/skill.txt", + "entry_id": 374, + "language_id": "plain-text" + }, + "47": { + "filepath": "/voyager/utils/__init__.py", + "entry_id": 380, + "language_id": "python" + }, + "48": { + "filepath": "/voyager/utils/file_utils.py", + "entry_id": 384, + "language_id": "python" + }, + "49": { + "filepath": "/voyager/utils/json_utils.py", + "entry_id": 416, + "language_id": "python" + }, + "50": { + "filepath": "/voyager/utils/record_utils.py", + "entry_id": 434, + "language_id": "python" + }, + "51": { + "filepath": "/voyager/voyager.py", + "entry_id": 444, + "language_id": "python" + } + }, + "project_name": "Voyager", + "split_count": 5 +} \ No newline at end of file diff --git a/docs/metadata_title.json b/docs/metadata_title.json new file mode 100644 index 00000000..0103dd27 --- /dev/null +++ b/docs/metadata_title.json @@ -0,0 +1 @@ +{"split_count": 1} \ No newline at end of file diff --git a/docs/sitemap.xml b/docs/sitemap.xml new file mode 100644 index 00000000..db1e1cbc --- /dev/null +++ b/docs/sitemap.xml @@ -0,0 +1,325 @@ + + + + + + + + https://james4ever0.github.io/Voyager?q=/FAQ.md + 2023-12-28T09:21:02+00:00 + 1.00 + + + + https://james4ever0.github.io/Voyager?q=/README.md + 2023-12-28T09:21:02+00:00 + 1.00 + + + + https://james4ever0.github.io/Voyager?q=/installation/fabric_mods_install.md + 2023-12-28T09:21:02+00:00 + 1.00 + + + + https://james4ever0.github.io/Voyager?q=/installation/minecraft_instance_install.md + 2023-12-28T09:21:02+00:00 + 1.00 + + + + https://james4ever0.github.io/Voyager?q=/requirements.txt + 2023-12-28T09:21:02+00:00 + 1.00 + + + + https://james4ever0.github.io/Voyager?q=/setup.py + 2023-12-28T09:21:02+00:00 + 1.00 + + + + https://james4ever0.github.io/Voyager?q=/skill_library/README.md + 2023-12-28T09:21:02+00:00 + 1.00 + + + + https://james4ever0.github.io/Voyager?q=/voyager/__init__.py + 2023-12-28T09:21:02+00:00 + 1.00 + + + + https://james4ever0.github.io/Voyager?q=/voyager/agents/__init__.py + 2023-12-28T09:21:02+00:00 + 1.00 + + + + https://james4ever0.github.io/Voyager?q=/voyager/agents/action.py + 2023-12-28T09:21:02+00:00 + 1.00 + + + + https://james4ever0.github.io/Voyager?q=/voyager/agents/critic.py + 2023-12-28T09:21:02+00:00 + 1.00 + + + + https://james4ever0.github.io/Voyager?q=/voyager/agents/curriculum.py + 2023-12-28T09:21:02+00:00 + 1.00 + + + + https://james4ever0.github.io/Voyager?q=/voyager/agents/skill.py + 2023-12-28T09:21:02+00:00 + 1.00 + + + + https://james4ever0.github.io/Voyager?q=/voyager/control_primitives/__init__.py + 2023-12-28T09:21:02+00:00 + 1.00 + + + + https://james4ever0.github.io/Voyager?q=/voyager/control_primitives/craftHelper.js + 2023-12-28T09:21:02+00:00 + 1.00 + + + + https://james4ever0.github.io/Voyager?q=/voyager/control_primitives/craftItem.js + 2023-12-28T09:21:02+00:00 + 1.00 + + + + https://james4ever0.github.io/Voyager?q=/voyager/control_primitives/exploreUntil.js + 2023-12-28T09:21:02+00:00 + 1.00 + + + + https://james4ever0.github.io/Voyager?q=/voyager/control_primitives/givePlacedItemBack.js + 2023-12-28T09:21:02+00:00 + 1.00 + + + + https://james4ever0.github.io/Voyager?q=/voyager/control_primitives/killMob.js + 2023-12-28T09:21:02+00:00 + 1.00 + + + + https://james4ever0.github.io/Voyager?q=/voyager/control_primitives/mineBlock.js + 2023-12-28T09:21:02+00:00 + 1.00 + + + + https://james4ever0.github.io/Voyager?q=/voyager/control_primitives/placeItem.js + 2023-12-28T09:21:02+00:00 + 1.00 + + + + https://james4ever0.github.io/Voyager?q=/voyager/control_primitives/shoot.js + 2023-12-28T09:21:02+00:00 + 1.00 + + + + https://james4ever0.github.io/Voyager?q=/voyager/control_primitives/smeltItem.js + 2023-12-28T09:21:02+00:00 + 1.00 + + + + https://james4ever0.github.io/Voyager?q=/voyager/control_primitives/useChest.js + 2023-12-28T09:21:02+00:00 + 1.00 + + + + https://james4ever0.github.io/Voyager?q=/voyager/control_primitives/waitForMobRemoved.js + 2023-12-28T09:21:02+00:00 + 1.00 + + + + https://james4ever0.github.io/Voyager?q=/voyager/control_primitives_context/__init__.py + 2023-12-28T09:21:02+00:00 + 1.00 + + + + https://james4ever0.github.io/Voyager?q=/voyager/control_primitives_context/craftItem.js + 2023-12-28T09:21:02+00:00 + 1.00 + + + + https://james4ever0.github.io/Voyager?q=/voyager/control_primitives_context/exploreUntil.js + 2023-12-28T09:21:02+00:00 + 1.00 + + + + https://james4ever0.github.io/Voyager?q=/voyager/control_primitives_context/killMob.js + 2023-12-28T09:21:02+00:00 + 1.00 + + + + https://james4ever0.github.io/Voyager?q=/voyager/control_primitives_context/mineBlock.js + 2023-12-28T09:21:02+00:00 + 1.00 + + + + https://james4ever0.github.io/Voyager?q=/voyager/control_primitives_context/mineflayer.js + 2023-12-28T09:21:02+00:00 + 1.00 + + + + https://james4ever0.github.io/Voyager?q=/voyager/control_primitives_context/placeItem.js + 2023-12-28T09:21:02+00:00 + 1.00 + + + + https://james4ever0.github.io/Voyager?q=/voyager/control_primitives_context/smeltItem.js + 2023-12-28T09:21:02+00:00 + 1.00 + + + + https://james4ever0.github.io/Voyager?q=/voyager/control_primitives_context/useChest.js + 2023-12-28T09:21:02+00:00 + 1.00 + + + + https://james4ever0.github.io/Voyager?q=/voyager/env/__init__.py + 2023-12-28T09:21:02+00:00 + 1.00 + + + + https://james4ever0.github.io/Voyager?q=/voyager/env/bridge.py + 2023-12-28T09:21:02+00:00 + 1.00 + + + + https://james4ever0.github.io/Voyager?q=/voyager/env/minecraft_launcher.py + 2023-12-28T09:21:02+00:00 + 1.00 + + + + https://james4ever0.github.io/Voyager?q=/voyager/env/process_monitor.py + 2023-12-28T09:21:02+00:00 + 1.00 + + + + https://james4ever0.github.io/Voyager?q=/voyager/prompts/__init__.py + 2023-12-28T09:21:02+00:00 + 1.00 + + + + https://james4ever0.github.io/Voyager?q=/voyager/prompts/action_response_format.txt + 2023-12-28T09:21:02+00:00 + 1.00 + + + + https://james4ever0.github.io/Voyager?q=/voyager/prompts/action_template.txt + 2023-12-28T09:21:02+00:00 + 1.00 + + + + https://james4ever0.github.io/Voyager?q=/voyager/prompts/critic.txt + 2023-12-28T09:21:02+00:00 + 1.00 + + + + https://james4ever0.github.io/Voyager?q=/voyager/prompts/curriculum.txt + 2023-12-28T09:21:02+00:00 + 1.00 + + + + https://james4ever0.github.io/Voyager?q=/voyager/prompts/curriculum_qa_step1_ask_questions.txt + 2023-12-28T09:21:02+00:00 + 1.00 + + + + https://james4ever0.github.io/Voyager?q=/voyager/prompts/curriculum_qa_step2_answer_questions.txt + 2023-12-28T09:21:02+00:00 + 1.00 + + + + https://james4ever0.github.io/Voyager?q=/voyager/prompts/curriculum_task_decomposition.txt + 2023-12-28T09:21:02+00:00 + 1.00 + + + + https://james4ever0.github.io/Voyager?q=/voyager/prompts/skill.txt + 2023-12-28T09:21:02+00:00 + 1.00 + + + + https://james4ever0.github.io/Voyager?q=/voyager/utils/__init__.py + 2023-12-28T09:21:02+00:00 + 1.00 + + + + https://james4ever0.github.io/Voyager?q=/voyager/utils/file_utils.py + 2023-12-28T09:21:02+00:00 + 1.00 + + + + https://james4ever0.github.io/Voyager?q=/voyager/utils/json_utils.py + 2023-12-28T09:21:02+00:00 + 1.00 + + + + https://james4ever0.github.io/Voyager?q=/voyager/utils/record_utils.py + 2023-12-28T09:21:02+00:00 + 1.00 + + + + https://james4ever0.github.io/Voyager?q=/voyager/voyager.py + 2023-12-28T09:21:02+00:00 + 1.00 + + + + https://james4ever0.github.io/Voyager/tree.html?full=true + 2023-12-28T09:21:02+00:00 + 1.00 + + + \ No newline at end of file diff --git a/docs/src/FAQ.md b/docs/src/FAQ.md new file mode 100644 index 00000000..893a828b --- /dev/null +++ b/docs/src/FAQ.md @@ -0,0 +1,54 @@ +# Frequently Asked Questions +* [I got connection error after I click on the Azure login link and login to Microsoft account.](#i-got-a-connection-error-after-i-click-on-the-azure-login-link-and-login-to-my-microsoft-account) +* [I got `KeyError: 'access_token'` after I copied the link](#i-got-keyerror-accesstoken-after-i-copied-the-link) +* [I got `Subprocess Mineflayer failed to start` error.](#i-got-subprocess-mineflayer-failed-to-start-error) +* [I saw the bot left and rejoin the game after each task.](#i-saw-the-bot-left-and-rejoin-the-game-after-each-task) +* [How to show the bot's first-person perspective?](#how-to-show-the-bots-first-person-view) +* [Can I use GPT-3.5 instead of GPT-4?](#can-i-use-gpt-35-instead-of-gpt-4) +* [What's the estimated cost of running Voyager?](#whats-the-estimated-cost-of-running-voyager) + +## I got a connection error after I click on the Azure login link and login to my Microsoft account. + +It's normal that you get a connection refused or 404 error after you log in. You will still see the new URL in your browser. You just need to copy and paste that link. It should contain things like `code=M.C....` in that link. + +## I got `KeyError: 'access_token'` after I copied the link + +While testing Voyager, we use Redirect URI Type: `Public client/native (mobile & desktop)` in the app registration for Azure Login. However, according to the report in issue [#34](https://github.com/MineDojo/Voyager/issues/34#issuecomment-1567007133), the URI Type was changed to "Web" and it resolved the problem. Feel free to attempt both URI Types to determine which one works for you. If all the approaches fail, please refer to the original tutorial in [minecraft-launcher-lib](https://minecraft-launcher-lib.readthedocs.io/en/stable/tutorial/microsoft_login.html). + +Update: This is probably a Microsoft's bug. See [issue #80 in minecraft-launcher-lib](https://codeberg.org/JakobDev/minecraft-launcher-lib/issues/80). If you cannot solve this problem, you can try to use the [Minecraft Official Launcher](./installation/minecraft_instance_install.md#option-2-minecraft-official-launcher) and use mc_port to run. +## I got `Subprocess Mineflayer failed to start` error. + +There are many reasons that may cause this problem. You can try with following solutions: +1. Make sure you install nodejs and the dependency packages correctly. You can use the following command to check your installation: + ```bash + cd voyager/env/mineflayer + node index.js + ``` + If you see `Server started on port {PORT}`, then your installation is correct. You can kill the process by `Ctrl+C`. +2. Make sure you install Fabric correctly. You should be able to select the Fabric version in the Minecraft launcher. +3. Each Mineflayer process can only listen to one port. If you want to start multiple instances of `Voyager`, you need to manually change the port when initialization: + ```python + from voyager import Voyager + voyager = Voyager( + server_port=3001, # default is 3000 + ... + ) + ``` + +## I saw the bot left and rejoin the game after each task. + +After completing each task, we'll reset the environment, which means the bot will exit and rejoin the game. This reset is necessary to synchronize Mineflayer with the Minecraft game. We do this because certain commands we utilize might result in lag on the Mineflayer side, causing the inventory stored in Mineflayer to differ from the actual inventory in the game. However, if you wish to avoid the reset, you can use `voyager.learn(reset_env=False)` and consider increasing the `env_wait_ticks` value. This will provide Mineflayer with additional time to sync with the Minecraft game. + + +## How to show the bot's first-person view? + +Due to the Mineflayer's limitation, we currently can not directly get the bot's view in the game. Although there's a plugin called [prismarine-viewer](https://github.com/PrismarineJS/prismarine-viewer), the video quality is not good enough, so we opt not to use it. Our demo video is generated by [replay-mod](https://www.replaymod.com/). We start the recording and let the bot play for hours, then come back to the recording and render the view from the bot. + + +## Can I use GPT-3.5 instead of GPT-4? + +It's highly recommended to use GPT-4. GPT-3.5 falls behind in terms of code quality and reasoning ability compared to GPT-4. Moreover, GPT-3.5 has a limited context length, which means it may provide incomplete responses. If you insist on using GPT-3.5, it is essential to configure it with `skill_manager_retrieval_top_k` ≤ 2 to reduce the context length of the prompt. + +## What's the estimated cost of running Voyager? + +Using Voyager for approximately 160 iterations using GPT-4 will cost you around 50 USD. It's important to keep a close eye on your OpenAI API expenses and avoid unnecessary spending. Once Voyager begins running, it's recommended to monitor the bot's behavior for a period and ensure that it successfully completes some tasks. \ No newline at end of file diff --git a/docs/src/README.md b/docs/src/README.md new file mode 100644 index 00000000..bc026e71 --- /dev/null +++ b/docs/src/README.md @@ -0,0 +1,164 @@ +# Voyager: An Open-Ended Embodied Agent with Large Language Models +
+ +[[Website]](https://voyager.minedojo.org/) +[[Arxiv]](https://arxiv.org/abs/2305.16291) +[[PDF]](https://voyager.minedojo.org/assets/documents/voyager.pdf) +[[Tweet]](https://twitter.com/DrJimFan/status/1662115266933972993?s=20) + +[![Python Version](https://img.shields.io/badge/Python-3.9-blue.svg)](https://github.com/MineDojo/Voyager) +[![GitHub license](https://img.shields.io/github/license/MineDojo/Voyager)](https://github.com/MineDojo/Voyager/blob/main/LICENSE) +______________________________________________________________________ + + +https://github.com/MineDojo/Voyager/assets/25460983/ce29f45b-43a5-4399-8fd8-5dd105fd64f2 + +![](images/pull.png) + + +
+ +We introduce Voyager, the first LLM-powered embodied lifelong learning agent +in Minecraft that continuously explores the world, acquires diverse skills, and +makes novel discoveries without human intervention. Voyager consists of three +key components: 1) an automatic curriculum that maximizes exploration, 2) an +ever-growing skill library of executable code for storing and retrieving complex +behaviors, and 3) a new iterative prompting mechanism that incorporates environment +feedback, execution errors, and self-verification for program improvement. +Voyager interacts with GPT-4 via blackbox queries, which bypasses the need for +model parameter fine-tuning. The skills developed by Voyager are temporally +extended, interpretable, and compositional, which compounds the agent’s abilities +rapidly and alleviates catastrophic forgetting. Empirically, Voyager shows +strong in-context lifelong learning capability and exhibits exceptional proficiency +in playing Minecraft. It obtains 3.3× more unique items, travels 2.3× longer +distances, and unlocks key tech tree milestones up to 15.3× faster than prior SOTA. +Voyager is able to utilize the learned skill library in a new Minecraft world to +solve novel tasks from scratch, while other techniques struggle to generalize. + +In this repo, we provide Voyager code. This codebase is under [MIT License](LICENSE). + +# Installation +Voyager requires Python ≥ 3.9 and Node.js ≥ 16.13.0. We have tested on Ubuntu 20.04, Windows 11, and macOS. You need to follow the instructions below to install Voyager. + +## Python Install +``` +git clone https://github.com/MineDojo/Voyager +cd Voyager +pip install -e . +``` + +## Node.js Install +In addition to the Python dependencies, you need to install the following Node.js packages: +``` +cd voyager/env/mineflayer +npm install -g npx +npm install +cd mineflayer-collectblock +npx tsc +cd .. +npm install +``` + +## Minecraft Instance Install + +Voyager depends on Minecraft game. You need to install Minecraft game and set up a Minecraft instance. + +Follow the instructions in [Minecraft Login Tutorial](installation/minecraft_instance_install.md) to set up your Minecraft Instance. + +## Fabric Mods Install + +You need to install fabric mods to support all the features in Voyager. Remember to use the correct Fabric version of all the mods. + +Follow the instructions in [Fabric Mods Install](installation/fabric_mods_install.md) to install the mods. + +# Getting Started +Voyager uses OpenAI's GPT-4 as the language model. You need to have an OpenAI API key to use Voyager. You can get one from [here](https://platform.openai.com/account/api-keys). + +After the installation process, you can run Voyager by: +```python +from voyager import Voyager + +# You can also use mc_port instead of azure_login, but azure_login is highly recommended +azure_login = { + "client_id": "YOUR_CLIENT_ID", + "redirect_url": "https://127.0.0.1/auth-response", + "secret_value": "[OPTIONAL] YOUR_SECRET_VALUE", + "version": "fabric-loader-0.14.18-1.19", # the version Voyager is tested on +} +openai_api_key = "YOUR_API_KEY" + +voyager = Voyager( + azure_login=azure_login, + openai_api_key=openai_api_key, +) + +# start lifelong learning +voyager.learn() +``` + +* If you are running with `Azure Login` for the first time, it will ask you to follow the command line instruction to generate a config file. +* For `Azure Login`, you also need to select the world and open the world to LAN by yourself. After you run `voyager.learn()` the game will pop up soon, you need to: + 1. Select `Singleplayer` and press `Create New World`. + 2. Set Game Mode to `Creative` and Difficulty to `Peaceful`. + 3. After the world is created, press `Esc` key and press `Open to LAN`. + 4. Select `Allow cheats: ON` and press `Start LAN World`. You will see the bot join the world soon. + +# Resume from a checkpoint during learning + +If you stop the learning process and want to resume from a checkpoint later, you can instantiate Voyager by: +```python +from voyager import Voyager + +voyager = Voyager( + azure_login=azure_login, + openai_api_key=openai_api_key, + ckpt_dir="YOUR_CKPT_DIR", + resume=True, +) +``` + +# Run Voyager for a specific task with a learned skill library + +If you want to run Voyager for a specific task with a learned skill library, you should first pass the skill library directory to Voyager: +```python +from voyager import Voyager + +# First instantiate Voyager with skill_library_dir. +voyager = Voyager( + azure_login=azure_login, + openai_api_key=openai_api_key, + skill_library_dir="./skill_library/trial1", # Load a learned skill library. + ckpt_dir="YOUR_CKPT_DIR", # Feel free to use a new dir. Do not use the same dir as skill library because new events will still be recorded to ckpt_dir. + resume=False, # Do not resume from a skill library because this is not learning. +) +``` +Then, you can run task decomposition. Notice: Occasionally, the task decomposition may not be logical. If you notice the printed sub-goals are flawed, you can rerun the decomposition. +```python +# Run task decomposition +task = "YOUR TASK" # e.g. "Craft a diamond pickaxe" +sub_goals = voyager.decompose_task(task=task) +``` +Finally, you can run the sub-goals with the learned skill library: +```python +voyager.inference(sub_goals=sub_goals) +``` + +For all valid skill libraries, see [Learned Skill Libraries](skill_library/README.md). + +# FAQ +If you have any questions, please check our [FAQ](FAQ.md) first before opening an issue. + +# Paper and Citation + +If you find our work useful, please consider citing us! + +```bibtex +@article{wang2023voyager, + title = {Voyager: An Open-Ended Embodied Agent with Large Language Models}, + author = {Guanzhi Wang and Yuqi Xie and Yunfan Jiang and Ajay Mandlekar and Chaowei Xiao and Yuke Zhu and Linxi Fan and Anima Anandkumar}, + year = {2023}, + journal = {arXiv preprint arXiv: Arxiv-2305.16291} +} +``` + +Disclaimer: This project is strictly for research purposes, and not an official product from NVIDIA. diff --git a/docs/src/installation/fabric_mods_install.md b/docs/src/installation/fabric_mods_install.md new file mode 100644 index 00000000..4aafd880 --- /dev/null +++ b/docs/src/installation/fabric_mods_install.md @@ -0,0 +1,22 @@ +# Fabric Mods Install +In this tutorial, we will install the Fabric launcher and 5 mods. Remember to use the correct Fabric version that matches your game version (1.19) of all the mods. +1. You can download the latest Fabric Installer from [here](https://fabricmc.net/use/installer/). For Windows users, just download the `.exe` file. For Mac or Ubuntu users, download the jar file and call `java -jar fabric-installer-0.11.2.jar` to install. Select game version to be `1.19` and loader version to be `0.14.18`. It will automatically detect your Minecraft game install location. +2. After installing Fabric, you will have a `YOUR_MINECRAFT_GAME_LOCATION/mods` folder. You need to put all the mods under this folder. Also, you will have a `YOUR_MINECRAFT_GAME_LOCATION/versions/fabric-loader-0.14.18-1.19`. This is the version you will run the game with. +3. Here are 4 mods that can be directly downloaded to `YOUR_MINECRAFT_GAME_LOCATION/mods` folder: + * [Fabric API](https://modrinth.com/mod/fabric-api/version/0.58.0+1.19): Basic Fabric APIs. + * [Mod Menu](https://cdn.modrinth.com/data/mOgUt4GM/versions/4.0.4/modmenu-4.0.4.jar): Used to manage all the mods that you download. + * [Complete Config](https://www.curseforge.com/minecraft/mc-mods/completeconfig/download/3821056): Dependency of server pause. + * [Multi Server Pause](https://www.curseforge.com/minecraft/mc-mods/multiplayer-server-pause-fabric/download/3822586): Used to pause the server when waiting for GPT-4 to reply. +4. For the last mod [Better Respawn](https://github.com/xieleo5/better-respawn/tree/1.19), you need to manually clone and compile. + + * After you clone the repo, remove the `'forge'` string in the last line of `settings.gradle`. Then run `gradlew build` to compile the mod. You will find the compiled jar file in `better-respawn/fabric/build/libs/better-respawn-fabric-1.19-2.0.0.jar`. Put the jar file to the mod folder. + * You will need a Java Runtime Environment v17+ to build `better-respawn`. Some newer JRE versions will error during build. Find the JRE v17 archive [here](https://www.oracle.com/java/technologies/javase/jdk17-archive-downloads.html). + * After you launch the game, go to `YOUR_MINECRAFT_GAME_LOCATION/config/better-respawn`, and modify the properties file with: + ``` + respawn_block_range=32 + max_respawn_distance=32 + min_respawn_distance=0 + ``` +5. Don't forget to change the `version` in `azure_login` to `fabric-loader-0.14.18-1.19` that you are using. You can find it under `YOUR_MINECRAFT_GAME_LOCATION/version` folder. + +You can return to [README.md](../README.md#getting-started) and getting started now. diff --git a/docs/src/installation/minecraft_instance_install.md b/docs/src/installation/minecraft_instance_install.md new file mode 100644 index 00000000..ec3b0126 --- /dev/null +++ b/docs/src/installation/minecraft_instance_install.md @@ -0,0 +1,42 @@ +# Minecraft Instance Install +To start using Voyager, you should first make sure to have an official [Minecraft](https://www.minecraft.net/) game (version 1.19) installed. + +There are two ways to start a Minecraft instance for Voyager. Sometimes GPT-4 will write an infinite loop that runs forever. In this case, there'll be a request timeout. Using Azure login can automatically resume the running if there's a request timeout. + +## Option 1: Microsoft Azure Login (Recommended) +Using this method will allow Voyager to automatically resume when there's a request timeout. This is dependent on the [minecraft-launcher-lib](https://minecraft-launcher-lib.readthedocs.io/en/stable/tutorial/microsoft_login.html#let-the-user-log-in) library. + +1. Sign in to [Azure Portal](https://portal.azure.com/). +2. Go to [Azure Active Directory](https://portal.azure.com/#blade/Microsoft_AAD_IAM/ActiveDirectoryMenuBlade/Overview). +3. Click on the `App Registrations` tab on the left panel. +4. Click on the `New registration` button. +5. Fill the form with the following values: + - Name: `YOUR_APP_NAME` + - Supported account types: `Accounts in any organizational directory (Any Azure AD directory - Multitenant) and personal Microsoft accounts` + - Redirect URI Type: `Public client/native (mobile & desktop)`, Value: `https://127.0.0.1/auth-response` (If you get `KeyError: 'access_token'` in the end, you can try to change the type to `Web`, see [FAQ](https://github.com/MineDojo/Voyager/blob/main/FAQ.md) for more information) +6. Click on the `Register` button. +7. The `Application (client) ID` will be your `client_id`. +8. [Optional] Go to the `Certificates & Secrets` tab and click on the `New client secret` button. Fill the description by yourself. After you click `Add`, you will see your value, this will be your `secret_value`. +9. Go to your Minecraft install location `YOUR_MINECRAFT_GAME_LOCATION/versions`, and check all the versions you have. All the folder names are your valid `version` value. + +After these steps, you will finally get your azure_login information: +```python +azure_login = { + "client_id": "CLIENT_ID FROM STEP 7", + "redirect_url": "https://127.0.0.1/auth-response", + "secret_value": "[OPTIONAL] SECRET_KEY FROM STEP 8", + "version": "MINECRAFT VERSION YOU WANT TO USE", +} +``` +**Voyager use `fabric-loader-0.14.18-1.19` version to run all the experiments.** You may not have this version currently, you can move on to the [Fabric Mods Install](fabric_mods_install.md#fabric-mods-install) section and follow the instructions there to install the fabric version of the game. + +## Option 2: Minecraft Official Launcher + +After you install official Minecraft, you should have a Minecraft official launcher, open it, and follow the instructions here: +1. Select the version you want to play and start the game. +2. Select `Singleplayer` and create a new world. +3. Set Game Mode to `Creative` and Difficulty to `Peaceful`. +4. After the world is created, press `Esc` and select `Open to LAN`. +5. Select `Allow cheats: ON` and press `Start LAN World`. +6. You will see a port number in the chat log, that is your `mc-port`, use this number to instantiate Voyager later. + diff --git a/docs/src/requirements.txt b/docs/src/requirements.txt new file mode 100644 index 00000000..47de4c45 --- /dev/null +++ b/docs/src/requirements.txt @@ -0,0 +1,14 @@ +tqdm +langchain +javascript +setuptools +openai +chardet +cchardet +chromadb==0.3.29 +tiktoken +requests +setuptools +gymnasium +psutil +minecraft_launcher_lib diff --git a/docs/src/setup.py b/docs/src/setup.py new file mode 100644 index 00000000..8ef258f4 --- /dev/null +++ b/docs/src/setup.py @@ -0,0 +1,59 @@ +import os +import pathlib +import pkg_resources +from setuptools import setup, find_packages + + +PKG_NAME = "voyager" +VERSION = "0.1" +EXTRAS = {} + + +def _read_file(fname): + # this_dir = os.path.abspath(os.path.dirname(__file__)) + # with open(os.path.join(this_dir, fname)) as f: + with pathlib.Path(fname).open(encoding="utf-8") as fp: + return fp.read() + + +def _read_install_requires(): + with pathlib.Path("requirements.txt").open() as fp: + return [ + str(requirement) for requirement in pkg_resources.parse_requirements(fp) + ] + + +def _fill_extras(extras): + if extras: + extras["all"] = list(set([item for group in extras.values() for item in group])) + return extras + + +setup( + name=PKG_NAME, + version=VERSION, + author=f"MineDojo Team", + url="https://github.com/MineDojo/Voyager", + description="research project", + long_description=_read_file("README.md"), + long_description_content_type="text/markdown", + keywords=[ + "Open-Ended Learning", + "Lifelong Learning", + "Embodied Agents", + "Large Language Models", + ], + license="MIT License", + packages=find_packages(include=f"{PKG_NAME}.*"), + include_package_data=True, + zip_safe=False, + install_requires=_read_install_requires(), + extras_require=_fill_extras(EXTRAS), + python_requires=">=3.9", + classifiers=[ + "Development Status :: 5 - Production/Stable", + "Topic :: Scientific/Engineering :: Artificial Intelligence", + "Environment :: Console", + "Programming Language :: Python :: 3.9", + ], +) diff --git a/docs/src/skill_library/README.md b/docs/src/skill_library/README.md new file mode 100644 index 00000000..476dc39a --- /dev/null +++ b/docs/src/skill_library/README.md @@ -0,0 +1,44 @@ +# Learned Skill Libraries + +## Ours + +* [skill_library/trial1](trial1) +* [skill_library/trial2](trial2) +* [skill_library/trial3](trial3) + +## Community Contributions + +* [daswer123/Voyager_checkpoint](https://github.com/daswer123/Voyager_checkpoint) +* [swen128/Voyager_checkpoint](https://github.com/swen128/Voyager_checkpoint) +* [DeveloperHarris/voyager_checkpoint](https://github.com/DeveloperHarris/voyager_checkpoint) + +### How to resume from a community contribution +First, you need to clone or download their repo. Then, the resume is the same as using ours skill libraries. Just set `skill_library_dir=COMMUNITY_CKPT_DIR` where `COMMUNITY_CKPT_DIR` is the ckpt dir inside the folder you just downloaded. + +## How to Contribute + +After you run the learning process, you will see a checkpoint directory like: +``` +. +├── action +│ └── chest_memory.json +├── curriculum +│ ├── completed_tasks.json +│ ├── failed_tasks.json +│ ├── qa_cache.json +│ └── vectordb +├── events +└── skill + ├── code + │ ├── catchThreeFishWithCheck.js + │ ├── collectBamboo.js + │ ├── ... + ├── description + │ ├── catchThreeFishWithCheck.txt + │ ├── collectBamboo.txt + │ └── ... + ├── skills.json + └── vectordb +``` + +Only `YOUR_CKPT_DIR/skill` is a learned skill library, which you can share with others. Create a pull request and add your skill library link to this page. diff --git a/docs/src/voyager/__init__.py b/docs/src/voyager/__init__.py new file mode 100644 index 00000000..412d1a9e --- /dev/null +++ b/docs/src/voyager/__init__.py @@ -0,0 +1 @@ +from .voyager import Voyager diff --git a/docs/src/voyager/agents/__init__.py b/docs/src/voyager/agents/__init__.py new file mode 100644 index 00000000..3989162c --- /dev/null +++ b/docs/src/voyager/agents/__init__.py @@ -0,0 +1,4 @@ +from .action import ActionAgent +from .critic import CriticAgent +from .curriculum import CurriculumAgent +from .skill import SkillManager diff --git a/docs/src/voyager/agents/action.py b/docs/src/voyager/agents/action.py new file mode 100644 index 00000000..b5b27ac0 --- /dev/null +++ b/docs/src/voyager/agents/action.py @@ -0,0 +1,280 @@ +import re +import time + +import voyager.utils as U +from javascript import require +from langchain.chat_models import ChatOpenAI +from langchain.prompts import SystemMessagePromptTemplate +from langchain.schema import AIMessage, HumanMessage, SystemMessage + +from voyager.prompts import load_prompt +from voyager.control_primitives_context import load_control_primitives_context + + +class ActionAgent: + def __init__( + self, + model_name="gpt-3.5-turbo", + temperature=0, + request_timout=120, + ckpt_dir="ckpt", + resume=False, + chat_log=True, + execution_error=True, + ): + self.ckpt_dir = ckpt_dir + self.chat_log = chat_log + self.execution_error = execution_error + U.f_mkdir(f"{ckpt_dir}/action") + if resume: + print(f"\033[32mLoading Action Agent from {ckpt_dir}/action\033[0m") + self.chest_memory = U.load_json(f"{ckpt_dir}/action/chest_memory.json") + else: + self.chest_memory = {} + self.llm = ChatOpenAI( + model_name=model_name, + temperature=temperature, + request_timeout=request_timout, + ) + + def update_chest_memory(self, chests): + for position, chest in chests.items(): + if position in self.chest_memory: + if isinstance(chest, dict): + self.chest_memory[position] = chest + if chest == "Invalid": + print( + f"\033[32mAction Agent removing chest {position}: {chest}\033[0m" + ) + self.chest_memory.pop(position) + else: + if chest != "Invalid": + print(f"\033[32mAction Agent saving chest {position}: {chest}\033[0m") + self.chest_memory[position] = chest + U.dump_json(self.chest_memory, f"{self.ckpt_dir}/action/chest_memory.json") + + def render_chest_observation(self): + chests = [] + for chest_position, chest in self.chest_memory.items(): + if isinstance(chest, dict) and len(chest) > 0: + chests.append(f"{chest_position}: {chest}") + for chest_position, chest in self.chest_memory.items(): + if isinstance(chest, dict) and len(chest) == 0: + chests.append(f"{chest_position}: Empty") + for chest_position, chest in self.chest_memory.items(): + if isinstance(chest, str): + assert chest == "Unknown" + chests.append(f"{chest_position}: Unknown items inside") + assert len(chests) == len(self.chest_memory) + if chests: + chests = "\n".join(chests) + return f"Chests:\n{chests}\n\n" + else: + return f"Chests: None\n\n" + + def render_system_message(self, skills=[]): + system_template = load_prompt("action_template") + # FIXME: Hardcoded control_primitives + base_skills = [ + "exploreUntil", + "mineBlock", + "craftItem", + "placeItem", + "smeltItem", + "killMob", + ] + if not self.llm.model_name == "gpt-3.5-turbo": + base_skills += [ + "useChest", + "mineflayer", + ] + programs = "\n\n".join(load_control_primitives_context(base_skills) + skills) + response_format = load_prompt("action_response_format") + system_message_prompt = SystemMessagePromptTemplate.from_template( + system_template + ) + system_message = system_message_prompt.format( + programs=programs, response_format=response_format + ) + assert isinstance(system_message, SystemMessage) + return system_message + + def render_human_message( + self, *, events, code="", task="", context="", critique="" + ): + chat_messages = [] + error_messages = [] + # FIXME: damage_messages is not used + damage_messages = [] + assert events[-1][0] == "observe", "Last event must be observe" + for i, (event_type, event) in enumerate(events): + if event_type == "onChat": + chat_messages.append(event["onChat"]) + elif event_type == "onError": + error_messages.append(event["onError"]) + elif event_type == "onDamage": + damage_messages.append(event["onDamage"]) + elif event_type == "observe": + biome = event["status"]["biome"] + time_of_day = event["status"]["timeOfDay"] + voxels = event["voxels"] + entities = event["status"]["entities"] + health = event["status"]["health"] + hunger = event["status"]["food"] + position = event["status"]["position"] + equipment = event["status"]["equipment"] + inventory_used = event["status"]["inventoryUsed"] + inventory = event["inventory"] + assert i == len(events) - 1, "observe must be the last event" + + observation = "" + + if code: + observation += f"Code from the last round:\n{code}\n\n" + else: + observation += f"Code from the last round: No code in the first round\n\n" + + if self.execution_error: + if error_messages: + error = "\n".join(error_messages) + observation += f"Execution error:\n{error}\n\n" + else: + observation += f"Execution error: No error\n\n" + + if self.chat_log: + if chat_messages: + chat_log = "\n".join(chat_messages) + observation += f"Chat log: {chat_log}\n\n" + else: + observation += f"Chat log: None\n\n" + + observation += f"Biome: {biome}\n\n" + + observation += f"Time: {time_of_day}\n\n" + + if voxels: + observation += f"Nearby blocks: {', '.join(voxels)}\n\n" + else: + observation += f"Nearby blocks: None\n\n" + + if entities: + nearby_entities = [ + k for k, v in sorted(entities.items(), key=lambda x: x[1]) + ] + observation += f"Nearby entities (nearest to farthest): {', '.join(nearby_entities)}\n\n" + else: + observation += f"Nearby entities (nearest to farthest): None\n\n" + + observation += f"Health: {health:.1f}/20\n\n" + + observation += f"Hunger: {hunger:.1f}/20\n\n" + + observation += f"Position: x={position['x']:.1f}, y={position['y']:.1f}, z={position['z']:.1f}\n\n" + + observation += f"Equipment: {equipment}\n\n" + + if inventory: + observation += f"Inventory ({inventory_used}/36): {inventory}\n\n" + else: + observation += f"Inventory ({inventory_used}/36): Empty\n\n" + + if not ( + task == "Place and deposit useless items into a chest" + or task.startswith("Deposit useless items into the chest at") + ): + observation += self.render_chest_observation() + + observation += f"Task: {task}\n\n" + + if context: + observation += f"Context: {context}\n\n" + else: + observation += f"Context: None\n\n" + + if critique: + observation += f"Critique: {critique}\n\n" + else: + observation += f"Critique: None\n\n" + + return HumanMessage(content=observation) + + def process_ai_message(self, message): + assert isinstance(message, AIMessage) + + retry = 3 + error = None + while retry > 0: + try: + babel = require("@babel/core") + babel_generator = require("@babel/generator").default + + code_pattern = re.compile(r"```(?:javascript|js)(.*?)```", re.DOTALL) + code = "\n".join(code_pattern.findall(message.content)) + parsed = babel.parse(code) + functions = [] + assert len(list(parsed.program.body)) > 0, "No functions found" + for i, node in enumerate(parsed.program.body): + if node.type != "FunctionDeclaration": + continue + node_type = ( + "AsyncFunctionDeclaration" + if node["async"] + else "FunctionDeclaration" + ) + functions.append( + { + "name": node.id.name, + "type": node_type, + "body": babel_generator(node).code, + "params": list(node["params"]), + } + ) + # find the last async function + main_function = None + for function in reversed(functions): + if function["type"] == "AsyncFunctionDeclaration": + main_function = function + break + assert ( + main_function is not None + ), "No async function found. Your main function must be async." + assert ( + len(main_function["params"]) == 1 + and main_function["params"][0].name == "bot" + ), f"Main function {main_function['name']} must take a single argument named 'bot'" + program_code = "\n\n".join(function["body"] for function in functions) + exec_code = f"await {main_function['name']}(bot);" + return { + "program_code": program_code, + "program_name": main_function["name"], + "exec_code": exec_code, + } + except Exception as e: + retry -= 1 + error = e + time.sleep(1) + return f"Error parsing action response (before program execution): {error}" + + def summarize_chatlog(self, events): + def filter_item(message: str): + craft_pattern = r"I cannot make \w+ because I need: (.*)" + craft_pattern2 = ( + r"I cannot make \w+ because there is no crafting table nearby" + ) + mine_pattern = r"I need at least a (.*) to mine \w+!" + if re.match(craft_pattern, message): + return re.match(craft_pattern, message).groups()[0] + elif re.match(craft_pattern2, message): + return "a nearby crafting table" + elif re.match(mine_pattern, message): + return re.match(mine_pattern, message).groups()[0] + else: + return "" + + chatlog = set() + for event_type, event in events: + if event_type == "onChat": + item = filter_item(event["onChat"]) + if item: + chatlog.add(item) + return "I also need " + ", ".join(chatlog) + "." if chatlog else "" diff --git a/docs/src/voyager/agents/critic.py b/docs/src/voyager/agents/critic.py new file mode 100644 index 00000000..34639bb8 --- /dev/null +++ b/docs/src/voyager/agents/critic.py @@ -0,0 +1,138 @@ +from voyager.prompts import load_prompt +from voyager.utils.json_utils import fix_and_parse_json +from langchain.chat_models import ChatOpenAI +from langchain.schema import HumanMessage, SystemMessage + + +class CriticAgent: + def __init__( + self, + model_name="gpt-3.5-turbo", + temperature=0, + request_timout=120, + mode="auto", + ): + self.llm = ChatOpenAI( + model_name=model_name, + temperature=temperature, + request_timeout=request_timout, + ) + assert mode in ["auto", "manual"] + self.mode = mode + + def render_system_message(self): + system_message = SystemMessage(content=load_prompt("critic")) + return system_message + + def render_human_message(self, *, events, task, context, chest_observation): + assert events[-1][0] == "observe", "Last event must be observe" + biome = events[-1][1]["status"]["biome"] + time_of_day = events[-1][1]["status"]["timeOfDay"] + voxels = events[-1][1]["voxels"] + health = events[-1][1]["status"]["health"] + hunger = events[-1][1]["status"]["food"] + position = events[-1][1]["status"]["position"] + equipment = events[-1][1]["status"]["equipment"] + inventory_used = events[-1][1]["status"]["inventoryUsed"] + inventory = events[-1][1]["inventory"] + + for i, (event_type, event) in enumerate(events): + if event_type == "onError": + print(f"\033[31mCritic Agent: Error occurs {event['onError']}\033[0m") + return None + + observation = "" + + observation += f"Biome: {biome}\n\n" + + observation += f"Time: {time_of_day}\n\n" + + if voxels: + observation += f"Nearby blocks: {', '.join(voxels)}\n\n" + else: + observation += f"Nearby blocks: None\n\n" + + observation += f"Health: {health:.1f}/20\n\n" + observation += f"Hunger: {hunger:.1f}/20\n\n" + + observation += f"Position: x={position['x']:.1f}, y={position['y']:.1f}, z={position['z']:.1f}\n\n" + + observation += f"Equipment: {equipment}\n\n" + + if inventory: + observation += f"Inventory ({inventory_used}/36): {inventory}\n\n" + else: + observation += f"Inventory ({inventory_used}/36): Empty\n\n" + + observation += chest_observation + + observation += f"Task: {task}\n\n" + + if context: + observation += f"Context: {context}\n\n" + else: + observation += f"Context: None\n\n" + + print(f"\033[31m****Critic Agent human message****\n{observation}\033[0m") + return HumanMessage(content=observation) + + def human_check_task_success(self): + confirmed = False + success = False + critique = "" + while not confirmed: + success = input("Success? (y/n)") + success = success.lower() == "y" + critique = input("Enter your critique:") + print(f"Success: {success}\nCritique: {critique}") + confirmed = input("Confirm? (y/n)") in ["y", ""] + return success, critique + + def ai_check_task_success(self, messages, max_retries=5): + if max_retries == 0: + print( + "\033[31mFailed to parse Critic Agent response. Consider updating your prompt.\033[0m" + ) + return False, "" + + if messages[1] is None: + return False, "" + + critic = self.llm(messages).content + print(f"\033[31m****Critic Agent ai message****\n{critic}\033[0m") + try: + response = fix_and_parse_json(critic) + assert response["success"] in [True, False] + if "critique" not in response: + response["critique"] = "" + return response["success"], response["critique"] + except Exception as e: + print(f"\033[31mError parsing critic response: {e} Trying again!\033[0m") + return self.ai_check_task_success( + messages=messages, + max_retries=max_retries - 1, + ) + + def check_task_success( + self, *, events, task, context, chest_observation, max_retries=5 + ): + human_message = self.render_human_message( + events=events, + task=task, + context=context, + chest_observation=chest_observation, + ) + + messages = [ + self.render_system_message(), + human_message, + ] + + if self.mode == "manual": + return self.human_check_task_success() + elif self.mode == "auto": + return self.ai_check_task_success( + messages=messages, max_retries=max_retries + ) + else: + raise ValueError(f"Invalid critic agent mode: {self.mode}") diff --git a/docs/src/voyager/agents/curriculum.py b/docs/src/voyager/agents/curriculum.py new file mode 100644 index 00000000..769c409f --- /dev/null +++ b/docs/src/voyager/agents/curriculum.py @@ -0,0 +1,498 @@ +from __future__ import annotations + +import random +import re + +import voyager.utils as U +from voyager.prompts import load_prompt +from voyager.utils.json_utils import fix_and_parse_json +from langchain.chat_models import ChatOpenAI +from langchain.embeddings.openai import OpenAIEmbeddings +from langchain.schema import HumanMessage, SystemMessage +from langchain.vectorstores import Chroma + + +class CurriculumAgent: + def __init__( + self, + model_name="gpt-3.5-turbo", + temperature=0, + qa_model_name="gpt-3.5-turbo", + qa_temperature=0, + request_timout=120, + ckpt_dir="ckpt", + resume=False, + mode="auto", + warm_up=None, + core_inventory_items: str | None = None, + ): + self.llm = ChatOpenAI( + model_name=model_name, + temperature=temperature, + request_timeout=request_timout, + ) + self.qa_llm = ChatOpenAI( + model_name=qa_model_name, + temperature=qa_temperature, + request_timeout=request_timout, + ) + assert mode in [ + "auto", + "manual", + ], f"mode {mode} not supported" + self.mode = mode + self.ckpt_dir = ckpt_dir + U.f_mkdir(f"{ckpt_dir}/curriculum/vectordb") + if resume: + print(f"\033[35mLoading Curriculum Agent from {ckpt_dir}/curriculum\033[0m") + self.completed_tasks = U.load_json( + f"{ckpt_dir}/curriculum/completed_tasks.json" + ) + self.failed_tasks = U.load_json(f"{ckpt_dir}/curriculum/failed_tasks.json") + self.qa_cache = U.load_json(f"{ckpt_dir}/curriculum/qa_cache.json") + else: + self.completed_tasks = [] + self.failed_tasks = [] + self.qa_cache = {} + # vectordb for qa cache + self.qa_cache_questions_vectordb = Chroma( + collection_name="qa_cache_questions_vectordb", + embedding_function=OpenAIEmbeddings(), + persist_directory=f"{ckpt_dir}/curriculum/vectordb", + ) + assert self.qa_cache_questions_vectordb._collection.count() == len( + self.qa_cache + ), ( + f"Curriculum Agent's qa cache question vectordb is not synced with qa_cache.json.\n" + f"There are {self.qa_cache_questions_vectordb._collection.count()} questions in vectordb " + f"but {len(self.qa_cache)} questions in qa_cache.json.\n" + f"Did you set resume=False when initializing the agent?\n" + f"You may need to manually delete the qa cache question vectordb directory for running from scratch.\n" + ) + # if warm up not defined, initialize it as a dict, else, initialize all the missing value as a default value + if not warm_up: + warm_up = self.default_warmup + self.warm_up = {} + if "optional_inventory_items" in warm_up: + assert core_inventory_items is not None + self._core_inv_items_regex = re.compile(core_inventory_items) + self.warm_up["optional_inventory_items"] = warm_up[ + "optional_inventory_items" + ] + else: + self.warm_up["optional_inventory_items"] = 0 + for key in self.curriculum_observations: + self.warm_up[key] = warm_up.get(key, self.default_warmup[key]) + self.warm_up["nearby_blocks"] = 0 + self.warm_up["inventory"] = 0 + self.warm_up["completed_tasks"] = 0 + self.warm_up["failed_tasks"] = 0 + + @property + def default_warmup(self): + return { + "context": 15, + "biome": 10, + "time": 15, + "nearby_blocks": 0, + "other_blocks": 10, + "nearby_entities": 5, + "health": 15, + "hunger": 15, + "position": 0, + "equipment": 0, + "inventory": 0, + "optional_inventory_items": 7, + "chests": 0, + "completed_tasks": 0, + "failed_tasks": 0, + } + + @property + def curriculum_observations(self): + return [ + "context", + "biome", + "time", + "nearby_blocks", + "other_blocks", + "nearby_entities", + "health", + "hunger", + "position", + "equipment", + "inventory", + "chests", + "completed_tasks", + "failed_tasks", + ] + + @property + def progress(self): + return len(self.completed_tasks) + + def render_system_message(self): + system_message = SystemMessage(content=load_prompt("curriculum")) + assert isinstance(system_message, SystemMessage) + return system_message + + def render_observation(self, *, events, chest_observation): + assert events[-1][0] == "observe", "Last event must be observe" + event = events[-1][1] + biome = event["status"]["biome"] + time_of_day = event["status"]["timeOfDay"] + voxels = event["voxels"] + block_records = event["blockRecords"] + entities = event["status"]["entities"] + health = event["status"]["health"] + hunger = event["status"]["food"] + position = event["status"]["position"] + equipment = event["status"]["equipment"] + inventory_used = event["status"]["inventoryUsed"] + inventory = event["inventory"] + + if not any( + "dirt" in block + or "log" in block + or "grass" in block + or "sand" in block + or "snow" in block + for block in voxels + ): + biome = "underground" + + other_blocks = ", ".join( + list( + set(block_records).difference(set(voxels).union(set(inventory.keys()))) + ) + ) + + other_blocks = other_blocks if other_blocks else "None" + + nearby_entities = ( + ", ".join([k for k, v in sorted(entities.items(), key=lambda x: x[1])]) + if entities + else "None" + ) + + completed_tasks = ( + ", ".join(self.completed_tasks) if self.completed_tasks else "None" + ) + failed_tasks = ", ".join(self.failed_tasks) if self.failed_tasks else "None" + + # filter out optional inventory items if required + if self.progress < self.warm_up["optional_inventory_items"]: + inventory = { + k: v + for k, v in inventory.items() + if self._core_inv_items_regex.search(k) is not None + } + + observation = { + "context": "", + "biome": f"Biome: {biome}\n\n", + "time": f"Time: {time_of_day}\n\n", + "nearby_blocks": f"Nearby blocks: {', '.join(voxels) if voxels else 'None'}\n\n", + "other_blocks": f"Other blocks that are recently seen: {other_blocks}\n\n", + "nearby_entities": f"Nearby entities: {nearby_entities}\n\n", + "health": f"Health: {health:.1f}/20\n\n", + "hunger": f"Hunger: {hunger:.1f}/20\n\n", + "position": f"Position: x={position['x']:.1f}, y={position['y']:.1f}, z={position['z']:.1f}\n\n", + "equipment": f"Equipment: {equipment}\n\n", + "inventory": f"Inventory ({inventory_used}/36): {inventory if inventory else 'Empty'}\n\n", + "chests": chest_observation, + "completed_tasks": f"Completed tasks so far: {completed_tasks}\n\n", + "failed_tasks": f"Failed tasks that are too hard: {failed_tasks}\n\n", + } + return observation + + def render_human_message(self, *, events, chest_observation): + content = "" + observation = self.render_observation( + events=events, chest_observation=chest_observation + ) + if self.progress >= self.warm_up["context"]: + questions, answers = self.run_qa( + events=events, chest_observation=chest_observation + ) + i = 1 + for question, answer in zip(questions, answers): + if "Answer: Unknown" in answer or "language model" in answer: + continue + observation["context"] += f"Question {i}: {question}\n" + observation["context"] += f"{answer}\n\n" + i += 1 + if i > 5: + break + + for key in self.curriculum_observations: + if self.progress >= self.warm_up[key]: + if self.warm_up[key] != 0: + should_include = random.random() < 0.8 + else: + should_include = True + if should_include: + content += observation[key] + + print(f"\033[35m****Curriculum Agent human message****\n{content}\033[0m") + return HumanMessage(content=content) + + def propose_next_task(self, *, events, chest_observation, max_retries=5): + if self.progress == 0 and self.mode == "auto": + task = "Mine 1 wood log" + context = "You can mine one of oak, birch, spruce, jungle, acacia, dark oak, or mangrove logs." + return task, context + + # hard code task when inventory is almost full + inventoryUsed = events[-1][1]["status"]["inventoryUsed"] + if inventoryUsed >= 33: + if chest_observation != "Chests: None\n\n": + chests = chest_observation[8:-2].split("\n") + for chest in chests: + content = chest.split(":")[1] + if content == " Unknown items inside" or content == " Empty": + position = chest.split(":")[0] + task = f"Deposit useless items into the chest at {position}" + context = ( + f"Your inventory have {inventoryUsed} occupied slots before depositing. " + "After depositing, your inventory should only have 20 occupied slots. " + "You should deposit useless items such as andesite, dirt, cobblestone, etc. " + "Also, you can deposit low-level tools, " + "For example, if you have a stone pickaxe, you can deposit a wooden pickaxe. " + "Make sure the list of useless items are in your inventory " + "(do not list items already in the chest), " + "You can use bot.inventoryUsed() to check how many inventory slots are used." + ) + return task, context + if "chest" in events[-1][1]["inventory"]: + task = "Place a chest" + context = ( + f"You have a chest in inventory, place it around you. " + f"If chests is not None, or nearby blocks contains chest, this task is success." + ) + else: + task = "Craft 1 chest" + context = "Craft 1 chest with 8 planks of any kind of wood." + return task, context + + messages = [ + self.render_system_message(), + self.render_human_message( + events=events, chest_observation=chest_observation + ), + ] + + if self.mode == "auto": + return self.propose_next_ai_task(messages=messages, max_retries=max_retries) + elif self.mode == "manual": + return self.propose_next_manual_task() + else: + raise ValueError(f"Invalid curriculum agent mode: {self.mode}") + + def propose_next_ai_task(self, *, messages, max_retries=5): + if max_retries == 0: + raise RuntimeError("Max retries reached, failed to propose ai task.") + curriculum = self.llm(messages).content + print(f"\033[31m****Curriculum Agent ai message****\n{curriculum}\033[0m") + try: + response = self.parse_ai_message(curriculum) + assert "next_task" in response + context = self.get_task_context(response["next_task"]) + return response["next_task"], context + except Exception as e: + print( + f"\033[35mError parsing curriculum response: {e}. Trying again!\033[0m" + ) + return self.propose_next_ai_task( + messages=messages, + max_retries=max_retries - 1, + ) + + def parse_ai_message(self, message): + task = "" + for line in message.split("\n"): + if line.startswith("Task:"): + task = line[5:].replace(".", "").strip() + assert task, "Task not found in Curriculum Agent response" + return {"next_task": task} + + def propose_next_manual_task(self): + confirmed = False + task, context = "", "" + while not confirmed: + task = input("Enter task: ") + context = input("Enter context: ") + print(f"Task: {task}\nContext: {context}") + confirmed = input("Confirm? (y/n)").lower() in ["y", ""] + return task, context + + def update_exploration_progress(self, info): + task = info["task"] + if task.startswith("Deposit useless items into the chest at"): + # No need to record the deposit task + return + if info["success"]: + print(f"\033[35mCompleted task {task}.\033[0m") + self.completed_tasks.append(task) + else: + print( + f"\033[35mFailed to complete task {task}. Skipping to next task.\033[0m" + ) + self.failed_tasks.append(task) + + # clean up tasks and dump to disk + self.clean_up_tasks() + + def clean_up_tasks(self): + updated_completed_tasks = [] + # record repeated failed tasks + updated_failed_tasks = self.failed_tasks + # dedup but keep order + for task in self.completed_tasks: + if task not in updated_completed_tasks: + updated_completed_tasks.append(task) + + # remove completed tasks from failed tasks + for task in updated_completed_tasks: + while task in updated_failed_tasks: + updated_failed_tasks.remove(task) + + self.completed_tasks = updated_completed_tasks + self.failed_tasks = updated_failed_tasks + + # dump to json + U.dump_json( + self.completed_tasks, f"{self.ckpt_dir}/curriculum/completed_tasks.json" + ) + U.dump_json(self.failed_tasks, f"{self.ckpt_dir}/curriculum/failed_tasks.json") + + def decompose_task(self, task, events): + messages = [ + SystemMessage( + content=load_prompt("curriculum_task_decomposition"), + ), + self.render_human_message(events=events, chest_observation=""), + HumanMessage(content=f"Final task: {task}"), + ] + print( + f"\033[31m****Curriculum Agent task decomposition****\nFinal task: {task}\033[0m" + ) + response = self.llm(messages).content + print(f"\033[31m****Curriculum Agent task decomposition****\n{response}\033[0m") + return fix_and_parse_json(response) + + def run_qa(self, *, events, chest_observation): + questions_new, _ = self.run_qa_step1_ask_questions( + events=events, chest_observation=chest_observation + ) + questions = [] + answers = [] + for question in questions_new: + if self.qa_cache_questions_vectordb._collection.count() > 0: + docs_and_scores = ( + self.qa_cache_questions_vectordb.similarity_search_with_score( + question, k=1 + ) + ) + if docs_and_scores and docs_and_scores[0][1] < 0.05: + question_cached = docs_and_scores[0][0].page_content + assert question_cached in self.qa_cache + answer_cached = self.qa_cache[question_cached] + questions.append(question_cached) + answers.append(answer_cached) + continue + answer = self.run_qa_step2_answer_questions(question=question) + assert question not in self.qa_cache + self.qa_cache[question] = answer + self.qa_cache_questions_vectordb.add_texts( + texts=[question], + ) + U.dump_json(self.qa_cache, f"{self.ckpt_dir}/curriculum/qa_cache.json") + self.qa_cache_questions_vectordb.persist() + questions.append(question) + answers.append(answer) + assert len(questions_new) == len(questions) == len(answers) + return questions, answers + + def get_task_context(self, task): + # if include ore in question, gpt will try to use tool with skill touch enhancement to mine + question = ( + f"How to {task.replace('_', ' ').replace(' ore', '').replace(' ores', '').replace('.', '').strip().lower()}" + f" in Minecraft?" + ) + if question in self.qa_cache: + answer = self.qa_cache[question] + else: + answer = self.run_qa_step2_answer_questions(question=question) + self.qa_cache[question] = answer + self.qa_cache_questions_vectordb.add_texts( + texts=[question], + ) + U.dump_json(self.qa_cache, f"{self.ckpt_dir}/curriculum/qa_cache.json") + self.qa_cache_questions_vectordb.persist() + context = f"Question: {question}\n{answer}" + return context + + def render_system_message_qa_step1_ask_questions(self): + return SystemMessage(content=load_prompt("curriculum_qa_step1_ask_questions")) + + def render_human_message_qa_step1_ask_questions(self, *, events, chest_observation): + observation = self.render_observation( + events=events, chest_observation=chest_observation + ) + content = "" + for key in self.curriculum_observations: + content += observation[key] + return HumanMessage(content=content) + + def run_qa_step1_ask_questions(self, *, events, chest_observation): + biome = events[-1][1]["status"]["biome"].replace("_", " ") + questions = [ + f"What are the blocks that I can find in the {biome} in Minecraft?", + f"What are the items that I can find in the {biome} in Minecraft?", + f"What are the mobs that I can find in the {biome} in Minecraft?", + ] + concepts = [biome, biome, biome] + messages = [ + self.render_system_message_qa_step1_ask_questions(), + self.render_human_message_qa_step1_ask_questions( + events=events, chest_observation=chest_observation + ), + ] + qa_response = self.qa_llm(messages).content + try: + # Regex pattern to extract question and concept pairs + pattern = r"Question \d+: (.+)\nConcept \d+: (.+)" + # Extracting all question and concept pairs from the text + pairs = re.findall(pattern, qa_response) + # Storing each question and concept in separate lists + questions_new = [pair[0] for pair in pairs] + concepts_new = [pair[1] for pair in pairs] + assert len(questions_new) == len(concepts_new) + questions.extend(questions_new) + concepts.extend(concepts_new) + except Exception as e: + print( + f"\033[35mError parsing curriculum response for " + f"QA step 1 ask questions: {e}.\033[0m" + ) + return questions, concepts + + def render_system_message_qa_step2_answer_questions(self): + return SystemMessage( + content=load_prompt("curriculum_qa_step2_answer_questions") + ) + + def render_human_message_qa_step2_answer_questions(self, question): + content = f"Question: {question}" + return HumanMessage(content=content) + + def run_qa_step2_answer_questions(self, question): + messages = [ + self.render_system_message_qa_step2_answer_questions(), + self.render_human_message_qa_step2_answer_questions(question=question), + ] + print(f"\033[35mCurriculum Agent Question: {question}\033[0m") + qa_answer = self.qa_llm(messages).content + print(f"\033[31mCurriculum Agent {qa_answer}\033[0m") + return qa_answer diff --git a/docs/src/voyager/agents/skill.py b/docs/src/voyager/agents/skill.py new file mode 100644 index 00000000..08499c78 --- /dev/null +++ b/docs/src/voyager/agents/skill.py @@ -0,0 +1,127 @@ +import os + +import voyager.utils as U +from langchain.chat_models import ChatOpenAI +from langchain.embeddings.openai import OpenAIEmbeddings +from langchain.schema import HumanMessage, SystemMessage +from langchain.vectorstores import Chroma + +from voyager.prompts import load_prompt +from voyager.control_primitives import load_control_primitives + + +class SkillManager: + def __init__( + self, + model_name="gpt-3.5-turbo", + temperature=0, + retrieval_top_k=5, + request_timout=120, + ckpt_dir="ckpt", + resume=False, + ): + self.llm = ChatOpenAI( + model_name=model_name, + temperature=temperature, + request_timeout=request_timout, + ) + U.f_mkdir(f"{ckpt_dir}/skill/code") + U.f_mkdir(f"{ckpt_dir}/skill/description") + U.f_mkdir(f"{ckpt_dir}/skill/vectordb") + # programs for env execution + self.control_primitives = load_control_primitives() + if resume: + print(f"\033[33mLoading Skill Manager from {ckpt_dir}/skill\033[0m") + self.skills = U.load_json(f"{ckpt_dir}/skill/skills.json") + else: + self.skills = {} + self.retrieval_top_k = retrieval_top_k + self.ckpt_dir = ckpt_dir + self.vectordb = Chroma( + collection_name="skill_vectordb", + embedding_function=OpenAIEmbeddings(), + persist_directory=f"{ckpt_dir}/skill/vectordb", + ) + assert self.vectordb._collection.count() == len(self.skills), ( + f"Skill Manager's vectordb is not synced with skills.json.\n" + f"There are {self.vectordb._collection.count()} skills in vectordb but {len(self.skills)} skills in skills.json.\n" + f"Did you set resume=False when initializing the manager?\n" + f"You may need to manually delete the vectordb directory for running from scratch." + ) + + @property + def programs(self): + programs = "" + for skill_name, entry in self.skills.items(): + programs += f"{entry['code']}\n\n" + for primitives in self.control_primitives: + programs += f"{primitives}\n\n" + return programs + + def add_new_skill(self, info): + if info["task"].startswith("Deposit useless items into the chest at"): + # No need to reuse the deposit skill + return + program_name = info["program_name"] + program_code = info["program_code"] + skill_description = self.generate_skill_description(program_name, program_code) + print( + f"\033[33mSkill Manager generated description for {program_name}:\n{skill_description}\033[0m" + ) + if program_name in self.skills: + print(f"\033[33mSkill {program_name} already exists. Rewriting!\033[0m") + self.vectordb._collection.delete(ids=[program_name]) + i = 2 + while f"{program_name}V{i}.js" in os.listdir(f"{self.ckpt_dir}/skill/code"): + i += 1 + dumped_program_name = f"{program_name}V{i}" + else: + dumped_program_name = program_name + self.vectordb.add_texts( + texts=[skill_description], + ids=[program_name], + metadatas=[{"name": program_name}], + ) + self.skills[program_name] = { + "code": program_code, + "description": skill_description, + } + assert self.vectordb._collection.count() == len( + self.skills + ), "vectordb is not synced with skills.json" + U.dump_text( + program_code, f"{self.ckpt_dir}/skill/code/{dumped_program_name}.js" + ) + U.dump_text( + skill_description, + f"{self.ckpt_dir}/skill/description/{dumped_program_name}.txt", + ) + U.dump_json(self.skills, f"{self.ckpt_dir}/skill/skills.json") + self.vectordb.persist() + + def generate_skill_description(self, program_name, program_code): + messages = [ + SystemMessage(content=load_prompt("skill")), + HumanMessage( + content=program_code + + "\n\n" + + f"The main function is `{program_name}`." + ), + ] + skill_description = f" // { self.llm(messages).content}" + return f"async function {program_name}(bot) {{\n{skill_description}\n}}" + + def retrieve_skills(self, query): + k = min(self.vectordb._collection.count(), self.retrieval_top_k) + if k == 0: + return [] + print(f"\033[33mSkill Manager retrieving for {k} skills\033[0m") + docs_and_scores = self.vectordb.similarity_search_with_score(query, k=k) + print( + f"\033[33mSkill Manager retrieved skills: " + f"{', '.join([doc.metadata['name'] for doc, _ in docs_and_scores])}\033[0m" + ) + skills = [] + for doc, _ in docs_and_scores: + skills.append(self.skills[doc.metadata["name"]]["code"]) + return skills diff --git a/docs/src/voyager/control_primitives/__init__.py b/docs/src/voyager/control_primitives/__init__.py new file mode 100644 index 00000000..369e587d --- /dev/null +++ b/docs/src/voyager/control_primitives/__init__.py @@ -0,0 +1,18 @@ +import pkg_resources +import os +import voyager.utils as U + + +def load_control_primitives(primitive_names=None): + package_path = pkg_resources.resource_filename("voyager", "") + if primitive_names is None: + primitive_names = [ + primitives[:-3] + for primitives in os.listdir(f"{package_path}/control_primitives") + if primitives.endswith(".js") + ] + primitives = [ + U.load_text(f"{package_path}/control_primitives/{primitive_name}.js") + for primitive_name in primitive_names + ] + return primitives diff --git a/docs/src/voyager/control_primitives/craftHelper.js b/docs/src/voyager/control_primitives/craftHelper.js new file mode 100644 index 00000000..41ae1f09 --- /dev/null +++ b/docs/src/voyager/control_primitives/craftHelper.js @@ -0,0 +1,61 @@ +function failedCraftFeedback(bot, name, item, craftingTable) { + const recipes = bot.recipesAll(item.id, null, craftingTable); + if (!recipes.length) { + throw new Error(`No crafting table nearby`); + } else { + const recipes = bot.recipesAll( + item.id, + null, + mcData.blocksByName.crafting_table.id + ); + // find the recipe with the fewest missing ingredients + var min = 999; + var min_recipe = null; + for (const recipe of recipes) { + const delta = recipe.delta; + var missing = 0; + for (const delta_item of delta) { + if (delta_item.count < 0) { + const inventory_item = bot.inventory.findInventoryItem( + mcData.items[delta_item.id].name, + null + ); + if (!inventory_item) { + missing += -delta_item.count; + } else { + missing += Math.max( + -delta_item.count - inventory_item.count, + 0 + ); + } + } + } + if (missing < min) { + min = missing; + min_recipe = recipe; + } + } + const delta = min_recipe.delta; + let message = ""; + for (const delta_item of delta) { + if (delta_item.count < 0) { + const inventory_item = bot.inventory.findInventoryItem( + mcData.items[delta_item.id].name, + null + ); + if (!inventory_item) { + message += ` ${-delta_item.count} more ${ + mcData.items[delta_item.id].name + }, `; + } else { + if (inventory_item.count < -delta_item.count) { + message += `${ + -delta_item.count - inventory_item.count + } more ${mcData.items[delta_item.id].name}`; + } + } + } + } + bot.chat(`I cannot make ${name} because I need: ${message}`); + } +} diff --git a/docs/src/voyager/control_primitives/craftItem.js b/docs/src/voyager/control_primitives/craftItem.js new file mode 100644 index 00000000..a2609058 --- /dev/null +++ b/docs/src/voyager/control_primitives/craftItem.js @@ -0,0 +1,43 @@ +async function craftItem(bot, name, count = 1) { + // return if name is not string + if (typeof name !== "string") { + throw new Error("name for craftItem must be a string"); + } + // return if count is not number + if (typeof count !== "number") { + throw new Error("count for craftItem must be a number"); + } + const itemByName = mcData.itemsByName[name]; + if (!itemByName) { + throw new Error(`No item named ${name}`); + } + const craftingTable = bot.findBlock({ + matching: mcData.blocksByName.crafting_table.id, + maxDistance: 32, + }); + if (!craftingTable) { + bot.chat("Craft without a crafting table"); + } else { + await bot.pathfinder.goto( + new GoalLookAtBlock(craftingTable.position, bot.world) + ); + } + const recipe = bot.recipesFor(itemByName.id, null, 1, craftingTable)[0]; + if (recipe) { + bot.chat(`I can make ${name}`); + try { + await bot.craft(recipe, count, craftingTable); + bot.chat(`I did the recipe for ${name} ${count} times`); + } catch (err) { + bot.chat(`I cannot do the recipe for ${name} ${count} times`); + } + } else { + failedCraftFeedback(bot, name, itemByName, craftingTable); + _craftItemFailCount++; + if (_craftItemFailCount > 10) { + throw new Error( + "craftItem failed too many times, check chat log to see what happened" + ); + } + } +} diff --git a/docs/src/voyager/control_primitives/exploreUntil.js b/docs/src/voyager/control_primitives/exploreUntil.js new file mode 100644 index 00000000..c73dcf3a --- /dev/null +++ b/docs/src/voyager/control_primitives/exploreUntil.js @@ -0,0 +1,87 @@ +// Explore downward for 60 seconds: exploreUntil(bot, new Vec3(0, -1, 0), 60); +async function exploreUntil( + bot, + direction, + maxTime = 60, + callback = () => { + return false; + } +) { + if (typeof maxTime !== "number") { + throw new Error("maxTime must be a number"); + } + if (typeof callback !== "function") { + throw new Error("callback must be a function"); + } + const test = callback(); + if (test) { + bot.chat("Explore success."); + return Promise.resolve(test); + } + if (direction.x === 0 && direction.y === 0 && direction.z === 0) { + throw new Error("direction cannot be 0, 0, 0"); + } + if ( + !( + (direction.x === 0 || direction.x === 1 || direction.x === -1) && + (direction.y === 0 || direction.y === 1 || direction.y === -1) && + (direction.z === 0 || direction.z === 1 || direction.z === -1) + ) + ) { + throw new Error( + "direction must be a Vec3 only with value of -1, 0 or 1" + ); + } + maxTime = Math.min(maxTime, 1200); + return new Promise((resolve, reject) => { + const dx = direction.x; + const dy = direction.y; + const dz = direction.z; + + let explorationInterval; + let maxTimeTimeout; + + const cleanUp = () => { + clearInterval(explorationInterval); + clearTimeout(maxTimeTimeout); + bot.pathfinder.setGoal(null); + }; + + const explore = () => { + const x = + bot.entity.position.x + + Math.floor(Math.random() * 20 + 10) * dx; + const y = + bot.entity.position.y + + Math.floor(Math.random() * 20 + 10) * dy; + const z = + bot.entity.position.z + + Math.floor(Math.random() * 20 + 10) * dz; + let goal = new GoalNear(x, y, z); + if (dy === 0) { + goal = new GoalNearXZ(x, z); + } + bot.pathfinder.setGoal(goal); + + try { + const result = callback(); + if (result) { + cleanUp(); + bot.chat("Explore success."); + resolve(result); + } + } catch (err) { + cleanUp(); + reject(err); + } + }; + + explorationInterval = setInterval(explore, 2000); + + maxTimeTimeout = setTimeout(() => { + cleanUp(); + bot.chat("Max exploration time reached"); + resolve(null); + }, maxTime * 1000); + }); +} diff --git a/docs/src/voyager/control_primitives/givePlacedItemBack.js b/docs/src/voyager/control_primitives/givePlacedItemBack.js new file mode 100644 index 00000000..57d3537f --- /dev/null +++ b/docs/src/voyager/control_primitives/givePlacedItemBack.js @@ -0,0 +1,38 @@ +async function givePlacedItemBack(bot, name, position) { + await bot.chat("/gamerule doTileDrops false"); + // iterate name and position + const history = []; + for (let i = 0; i < name.length; i++) { + await givePlacedItemBackSingle(bot, name[i], position[i]); + } + await bot.chat("/gamerule doTileDrops true"); + + async function givePlacedItemBackSingle(bot, name, position) { + bot.chat(`/give bot ${name} 1`); + const x = Math.floor(position.x); + const y = Math.floor(position.y); + const z = Math.floor(position.z); + // loop through 125 blocks around the block + const size = 3; + for (let dx = -size; dx <= size; dx++) { + for (let dy = -size; dy <= size; dy++) { + for (let dz = -size; dz <= size; dz++) { + const block = bot.blockAt(new Vec3(x + dx, y + dy, z + dz)); + if ( + block?.name === name && + !history.includes(block.position) + ) { + await bot.chat( + `/setblock ${x + dx} ${y + dy} ${ + z + dz + } air destroy` + ); + history.push(block.position); + await bot.waitForTicks(20); + return; + } + } + } + } + } +} diff --git a/docs/src/voyager/control_primitives/killMob.js b/docs/src/voyager/control_primitives/killMob.js new file mode 100644 index 00000000..3466077b --- /dev/null +++ b/docs/src/voyager/control_primitives/killMob.js @@ -0,0 +1,51 @@ +async function killMob(bot, mobName, timeout = 300) { + // return if mobName is not string + if (typeof mobName !== "string") { + throw new Error(`mobName for killMob must be a string`); + } + // return if timeout is not number + if (typeof timeout !== "number") { + throw new Error(`timeout for killMob must be a number`); + } + + const weaponsForShooting = [ + "bow", + "crossbow", + "snowball", + "ender_pearl", + "egg", + "splash_potion", + "trident", + ]; + const mainHandItem = bot.inventory.slots[bot.getEquipmentDestSlot("hand")]; + + const entity = bot.nearestEntity( + (entity) => + entity.name === mobName && + // kill mob distance should be slightly bigger than explore distance + entity.position.distanceTo(bot.entity.position) < 48 + ); + if (!entity) { + bot.chat(`No ${mobName} nearby, please explore first`); + _killMobFailCount++; + if (_killMobFailCount > 10) { + throw new Error( + `killMob failed too many times, make sure you explore before calling killMob` + ); + } + return; + } + + let droppedItem; + if (mainHandItem && weaponsForShooting.includes(mainHandItem.name)) { + bot.hawkEye.autoAttack(entity, mainHandItem.name); + droppedItem = await waitForMobShot(bot, entity, timeout); + } else { + await bot.pvp.attack(entity); + droppedItem = await waitForMobRemoved(bot, entity, timeout); + } + if (droppedItem) { + await bot.collectBlock.collect(droppedItem, { ignoreNoPath: true }); + } + bot.save(`${mobName}_killed`); +} diff --git a/docs/src/voyager/control_primitives/mineBlock.js b/docs/src/voyager/control_primitives/mineBlock.js new file mode 100644 index 00000000..5746091f --- /dev/null +++ b/docs/src/voyager/control_primitives/mineBlock.js @@ -0,0 +1,37 @@ +async function mineBlock(bot, name, count = 1) { + // return if name is not string + if (typeof name !== "string") { + throw new Error(`name for mineBlock must be a string`); + } + if (typeof count !== "number") { + throw new Error(`count for mineBlock must be a number`); + } + const blockByName = mcData.blocksByName[name]; + if (!blockByName) { + throw new Error(`No block named ${name}`); + } + const blocks = bot.findBlocks({ + matching: [blockByName.id], + maxDistance: 32, + count: 1024, + }); + if (blocks.length === 0) { + bot.chat(`No ${name} nearby, please explore first`); + _mineBlockFailCount++; + if (_mineBlockFailCount > 10) { + throw new Error( + "mineBlock failed too many times, make sure you explore before calling mineBlock" + ); + } + return; + } + const targets = []; + for (let i = 0; i < blocks.length; i++) { + targets.push(bot.blockAt(blocks[i])); + } + await bot.collectBlock.collect(targets, { + ignoreNoPath: true, + count: count, + }); + bot.save(`${name}_mined`); +} diff --git a/docs/src/voyager/control_primitives/placeItem.js b/docs/src/voyager/control_primitives/placeItem.js new file mode 100644 index 00000000..90175a7c --- /dev/null +++ b/docs/src/voyager/control_primitives/placeItem.js @@ -0,0 +1,79 @@ +async function placeItem(bot, name, position) { + // return if name is not string + if (typeof name !== "string") { + throw new Error(`name for placeItem must be a string`); + } + // return if position is not Vec3 + if (!(position instanceof Vec3)) { + throw new Error(`position for placeItem must be a Vec3`); + } + const itemByName = mcData.itemsByName[name]; + if (!itemByName) { + throw new Error(`No item named ${name}`); + } + const item = bot.inventory.findInventoryItem(itemByName.id); + if (!item) { + bot.chat(`No ${name} in inventory`); + return; + } + const item_count = item.count; + // find a reference block + const faceVectors = [ + new Vec3(0, 1, 0), + new Vec3(0, -1, 0), + new Vec3(1, 0, 0), + new Vec3(-1, 0, 0), + new Vec3(0, 0, 1), + new Vec3(0, 0, -1), + ]; + let referenceBlock = null; + let faceVector = null; + for (const vector of faceVectors) { + const block = bot.blockAt(position.minus(vector)); + if (block?.name !== "air") { + referenceBlock = block; + faceVector = vector; + bot.chat(`Placing ${name} on ${block.name} at ${block.position}`); + break; + } + } + if (!referenceBlock) { + bot.chat( + `No block to place ${name} on. You cannot place a floating block.` + ); + _placeItemFailCount++; + if (_placeItemFailCount > 10) { + throw new Error( + `placeItem failed too many times. You cannot place a floating block.` + ); + } + return; + } + + // You must use try catch to placeBlock + try { + // You must first go to the block position you want to place + await bot.pathfinder.goto(new GoalPlaceBlock(position, bot.world, {})); + // You must equip the item right before calling placeBlock + await bot.equip(item, "hand"); + await bot.placeBlock(referenceBlock, faceVector); + bot.chat(`Placed ${name}`); + bot.save(`${name}_placed`); + } catch (err) { + const item = bot.inventory.findInventoryItem(itemByName.id); + if (item?.count === item_count) { + bot.chat( + `Error placing ${name}: ${err.message}, please find another position to place` + ); + _placeItemFailCount++; + if (_placeItemFailCount > 10) { + throw new Error( + `placeItem failed too many times, please find another position to place.` + ); + } + } else { + bot.chat(`Placed ${name}`); + bot.save(`${name}_placed`); + } + } +} diff --git a/docs/src/voyager/control_primitives/shoot.js b/docs/src/voyager/control_primitives/shoot.js new file mode 100644 index 00000000..c0f862a0 --- /dev/null +++ b/docs/src/voyager/control_primitives/shoot.js @@ -0,0 +1,34 @@ +// shoot 1 pig with a bow: shoot(bot, "bow", "pig"); +async function shoot(bot, weapon, target) { + const validWeapons = [ + "bow", + "crossbow", + "snowball", + "ender_pearl", + "egg", + "splash_potion", + "trident", + ]; + if (!validWeapons.includes(weapon)) { + bot.chat(`${weapon} is not a valid weapon for shooting`); + return; + } + + const weaponItem = mcData.itemsByName[weapon]; + if (!bot.inventory.findInventoryItem(weaponItem.id, null)) { + bot.chat(`No ${weapon} in inventory for shooting`); + return; + } + + const targetEntity = bot.nearestEntity( + (entity) => + entity.name === target + ); + if (!targetEntity) { + bot.chat(`No ${target} nearby`); + return; + } + bot.hawkEye.autoAttack(targetEntity, "bow"); + bot.on('auto_shot_stopped', (target) => { + }) +} diff --git a/docs/src/voyager/control_primitives/smeltItem.js b/docs/src/voyager/control_primitives/smeltItem.js new file mode 100644 index 00000000..4f06817a --- /dev/null +++ b/docs/src/voyager/control_primitives/smeltItem.js @@ -0,0 +1,68 @@ +async function smeltItem(bot, itemName, fuelName, count = 1) { + // return if itemName or fuelName is not string + if (typeof itemName !== "string" || typeof fuelName !== "string") { + throw new Error("itemName or fuelName for smeltItem must be a string"); + } + // return if count is not a number + if (typeof count !== "number") { + throw new Error("count for smeltItem must be a number"); + } + const item = mcData.itemsByName[itemName]; + const fuel = mcData.itemsByName[fuelName]; + if (!item) { + throw new Error(`No item named ${itemName}`); + } + if (!fuel) { + throw new Error(`No item named ${fuelName}`); + } + const furnaceBlock = bot.findBlock({ + matching: mcData.blocksByName.furnace.id, + maxDistance: 32, + }); + if (!furnaceBlock) { + throw new Error("No furnace nearby"); + } else { + await bot.pathfinder.goto( + new GoalLookAtBlock(furnaceBlock.position, bot.world) + ); + } + const furnace = await bot.openFurnace(furnaceBlock); + let success_count = 0; + for (let i = 0; i < count; i++) { + if (!bot.inventory.findInventoryItem(item.id, null)) { + bot.chat(`No ${itemName} to smelt in inventory`); + break; + } + if (furnace.fuelSeconds < 15 && furnace.fuelItem()?.name !== fuelName) { + if (!bot.inventory.findInventoryItem(fuel.id, null)) { + bot.chat(`No ${fuelName} as fuel in inventory`); + break; + } + await furnace.putFuel(fuel.id, null, 1); + await bot.waitForTicks(20); + if (!furnace.fuel && furnace.fuelItem()?.name !== fuelName) { + throw new Error(`${fuelName} is not a valid fuel`); + } + } + await furnace.putInput(item.id, null, 1); + await bot.waitForTicks(12 * 20); + if (!furnace.outputItem()) { + throw new Error(`${itemName} is not a valid input`); + } + await furnace.takeOutput(); + success_count++; + } + furnace.close(); + if (success_count > 0) bot.chat(`Smelted ${success_count} ${itemName}.`); + else { + bot.chat( + `Failed to smelt ${itemName}, please check the fuel and input.` + ); + _smeltItemFailCount++; + if (_smeltItemFailCount > 10) { + throw new Error( + `smeltItem failed too many times, please check the fuel and input.` + ); + } + } +} diff --git a/docs/src/voyager/control_primitives/useChest.js b/docs/src/voyager/control_primitives/useChest.js new file mode 100644 index 00000000..64f02da8 --- /dev/null +++ b/docs/src/voyager/control_primitives/useChest.js @@ -0,0 +1,133 @@ +async function getItemFromChest(bot, chestPosition, itemsToGet) { + // return if chestPosition is not Vec3 + if (!(chestPosition instanceof Vec3)) { + bot.chat("chestPosition for getItemFromChest must be a Vec3"); + return; + } + await moveToChest(bot, chestPosition); + const chestBlock = bot.blockAt(chestPosition); + const chest = await bot.openContainer(chestBlock); + for (const name in itemsToGet) { + const itemByName = mcData.itemsByName[name]; + if (!itemByName) { + bot.chat(`No item named ${name}`); + continue; + } + + const item = chest.findContainerItem(itemByName.id); + if (!item) { + bot.chat(`I don't see ${name} in this chest`); + continue; + } + try { + await chest.withdraw(item.type, null, itemsToGet[name]); + } catch (err) { + bot.chat(`Not enough ${name} in chest.`); + } + } + await closeChest(bot, chestBlock); +} + +async function depositItemIntoChest(bot, chestPosition, itemsToDeposit) { + // return if chestPosition is not Vec3 + if (!(chestPosition instanceof Vec3)) { + throw new Error( + "chestPosition for depositItemIntoChest must be a Vec3" + ); + } + await moveToChest(bot, chestPosition); + const chestBlock = bot.blockAt(chestPosition); + const chest = await bot.openContainer(chestBlock); + for (const name in itemsToDeposit) { + const itemByName = mcData.itemsByName[name]; + if (!itemByName) { + bot.chat(`No item named ${name}`); + continue; + } + const item = bot.inventory.findInventoryItem(itemByName.id); + if (!item) { + bot.chat(`No ${name} in inventory`); + continue; + } + try { + await chest.deposit(item.type, null, itemsToDeposit[name]); + } catch (err) { + bot.chat(`Not enough ${name} in inventory.`); + } + } + await closeChest(bot, chestBlock); +} + +async function checkItemInsideChest(bot, chestPosition) { + // return if chestPosition is not Vec3 + if (!(chestPosition instanceof Vec3)) { + throw new Error( + "chestPosition for depositItemIntoChest must be a Vec3" + ); + } + await moveToChest(bot, chestPosition); + const chestBlock = bot.blockAt(chestPosition); + await bot.openContainer(chestBlock); + await closeChest(bot, chestBlock); +} + +async function moveToChest(bot, chestPosition) { + if (!(chestPosition instanceof Vec3)) { + throw new Error( + "chestPosition for depositItemIntoChest must be a Vec3" + ); + } + if (chestPosition.distanceTo(bot.entity.position) > 32) { + bot.chat( + `/tp ${chestPosition.x} ${chestPosition.y} ${chestPosition.z}` + ); + await bot.waitForTicks(20); + } + const chestBlock = bot.blockAt(chestPosition); + if (chestBlock.name !== "chest") { + bot.emit("removeChest", chestPosition); + throw new Error( + `No chest at ${chestPosition}, it is ${chestBlock.name}` + ); + } + await bot.pathfinder.goto( + new GoalLookAtBlock(chestBlock.position, bot.world, {}) + ); + return chestBlock; +} + +async function listItemsInChest(bot, chestBlock) { + const chest = await bot.openContainer(chestBlock); + const items = chest.containerItems(); + if (items.length > 0) { + const itemNames = items.reduce((acc, obj) => { + if (acc[obj.name]) { + acc[obj.name] += obj.count; + } else { + acc[obj.name] = obj.count; + } + return acc; + }, {}); + bot.emit("closeChest", itemNames, chestBlock.position); + } else { + bot.emit("closeChest", {}, chestBlock.position); + } + return chest; +} + +async function closeChest(bot, chestBlock) { + try { + const chest = await listItemsInChest(bot, chestBlock); + await chest.close(); + } catch (err) { + await bot.closeWindow(chestBlock); + } +} + +function itemByName(items, name) { + for (let i = 0; i < items.length; ++i) { + const item = items[i]; + if (item && item.name === name) return item; + } + return null; +} diff --git a/docs/src/voyager/control_primitives/waitForMobRemoved.js b/docs/src/voyager/control_primitives/waitForMobRemoved.js new file mode 100644 index 00000000..fa83d431 --- /dev/null +++ b/docs/src/voyager/control_primitives/waitForMobRemoved.js @@ -0,0 +1,84 @@ +function waitForMobRemoved(bot, entity, timeout = 300) { + return new Promise((resolve, reject) => { + let success = false; + let droppedItem = null; + // Set up timeout + const timeoutId = setTimeout(() => { + success = false; + bot.pvp.stop(); + }, timeout * 1000); + + // Function to handle entityRemoved event + function onEntityGone(e) { + if (e === entity) { + success = true; + clearTimeout(timeoutId); + bot.chat(`Killed ${entity.name}!`); + bot.pvp.stop(); + } + } + + function onItemDrop(item) { + if (entity.position.distanceTo(item.position) <= 1) { + droppedItem = item; + } + } + + function onStoppedAttacking() { + clearTimeout(timeoutId); + bot.removeListener("entityGone", onEntityGone); + bot.removeListener("stoppedAttacking", onStoppedAttacking); + bot.removeListener("itemDrop", onItemDrop); + if (!success) reject(new Error(`Failed to kill ${entity.name}.`)); + else resolve(droppedItem); + } + + // Listen for entityRemoved event + bot.on("entityGone", onEntityGone); + bot.on("stoppedAttacking", onStoppedAttacking); + bot.on("itemDrop", onItemDrop); + }); +} + + +function waitForMobShot(bot, entity, timeout = 300) { + return new Promise((resolve, reject) => { + let success = false; + let droppedItem = null; + // Set up timeout + const timeoutId = setTimeout(() => { + success = false; + bot.hawkEye.stop(); + }, timeout * 1000); + + // Function to handle entityRemoved event + function onEntityGone(e) { + if (e === entity) { + success = true; + clearTimeout(timeoutId); + bot.chat(`Shot ${entity.name}!`); + bot.hawkEye.stop(); + } + } + + function onItemDrop(item) { + if (entity.position.distanceTo(item.position) <= 1) { + droppedItem = item; + } + } + + function onAutoShotStopped() { + clearTimeout(timeoutId); + bot.removeListener("entityGone", onEntityGone); + bot.removeListener("auto_shot_stopped", onAutoShotStopped); + bot.removeListener("itemDrop", onItemDrop); + if (!success) reject(new Error(`Failed to shoot ${entity.name}.`)); + else resolve(droppedItem); + } + + // Listen for entityRemoved event + bot.on("entityGone", onEntityGone); + bot.on("auto_shot_stopped", onAutoShotStopped); + bot.on("itemDrop", onItemDrop); + }); +} diff --git a/docs/src/voyager/control_primitives_context/__init__.py b/docs/src/voyager/control_primitives_context/__init__.py new file mode 100644 index 00000000..8126c39f --- /dev/null +++ b/docs/src/voyager/control_primitives_context/__init__.py @@ -0,0 +1,18 @@ +import pkg_resources +import os +import voyager.utils as U + + +def load_control_primitives_context(primitive_names=None): + package_path = pkg_resources.resource_filename("voyager", "") + if primitive_names is None: + primitive_names = [ + primitive[:-3] + for primitive in os.listdir(f"{package_path}/control_primitives_context") + if primitive.endswith(".js") + ] + primitives = [ + U.load_text(f"{package_path}/control_primitives_context/{primitive_name}.js") + for primitive_name in primitive_names + ] + return primitives diff --git a/docs/src/voyager/control_primitives_context/craftItem.js b/docs/src/voyager/control_primitives_context/craftItem.js new file mode 100644 index 00000000..806811d4 --- /dev/null +++ b/docs/src/voyager/control_primitives_context/craftItem.js @@ -0,0 +1,14 @@ +// Craft 8 oak_planks from 2 oak_log (do the recipe 2 times): craftItem(bot, "oak_planks", 2); +// You must place a crafting table before calling this function +async function craftItem(bot, name, count = 1) { + const item = mcData.itemsByName[name]; + const craftingTable = bot.findBlock({ + matching: mcData.blocksByName.crafting_table.id, + maxDistance: 32, + }); + await bot.pathfinder.goto( + new GoalLookAtBlock(craftingTable.position, bot.world) + ); + const recipe = bot.recipesFor(item.id, null, 1, craftingTable)[0]; + await bot.craft(recipe, count, craftingTable); +} diff --git a/docs/src/voyager/control_primitives_context/exploreUntil.js b/docs/src/voyager/control_primitives_context/exploreUntil.js new file mode 100644 index 00000000..55c62a45 --- /dev/null +++ b/docs/src/voyager/control_primitives_context/exploreUntil.js @@ -0,0 +1,31 @@ +/* +Explore until find an iron_ore, use Vec3(0, -1, 0) because iron ores are usually underground +await exploreUntil(bot, new Vec3(0, -1, 0), 60, () => { + const iron_ore = bot.findBlock({ + matching: mcData.blocksByName["iron_ore"].id, + maxDistance: 32, + }); + return iron_ore; +}); + +Explore until find a pig, use Vec3(1, 0, 1) because pigs are usually on the surface +let pig = await exploreUntil(bot, new Vec3(1, 0, 1), 60, () => { + const pig = bot.nearestEntity((entity) => { + return ( + entity.name === "pig" && + entity.position.distanceTo(bot.entity.position) < 32 + ); + }); + return pig; +}); +*/ +async function exploreUntil(bot, direction, maxTime = 60, callback) { + /* + Implementation of this function is omitted. + direction: Vec3, can only contain value of -1, 0 or 1 + maxTime: number, the max time for exploration + callback: function, early stop condition, will be called each second, exploration will stop if return value is not null + + Return: null if explore timeout, otherwise return the return value of callback + */ +} diff --git a/docs/src/voyager/control_primitives_context/killMob.js b/docs/src/voyager/control_primitives_context/killMob.js new file mode 100644 index 00000000..670ca975 --- /dev/null +++ b/docs/src/voyager/control_primitives_context/killMob.js @@ -0,0 +1,12 @@ +// Kill a pig and collect the dropped item: killMob(bot, "pig", 300); +async function killMob(bot, mobName, timeout = 300) { + const entity = bot.nearestEntity( + (entity) => + entity.name === mobName && + entity.position.distanceTo(bot.entity.position) < 32 + ); + await bot.pvp.attack(entity); + await bot.pathfinder.goto( + new GoalBlock(entity.position.x, entity.position.y, entity.position.z) + ); +} diff --git a/docs/src/voyager/control_primitives_context/mineBlock.js b/docs/src/voyager/control_primitives_context/mineBlock.js new file mode 100644 index 00000000..c6a7559e --- /dev/null +++ b/docs/src/voyager/control_primitives_context/mineBlock.js @@ -0,0 +1,15 @@ +// Mine 3 cobblestone: mineBlock(bot, "stone", 3); +async function mineBlock(bot, name, count = 1) { + const blocks = bot.findBlocks({ + matching: (block) => { + return block.name === name; + }, + maxDistance: 32, + count: count, + }); + const targets = []; + for (let i = 0; i < Math.min(blocks.length, count); i++) { + targets.push(bot.blockAt(blocks[i])); + } + await bot.collectBlock.collect(targets, { ignoreNoPath: true }); +} diff --git a/docs/src/voyager/control_primitives_context/mineflayer.js b/docs/src/voyager/control_primitives_context/mineflayer.js new file mode 100644 index 00000000..43217885 --- /dev/null +++ b/docs/src/voyager/control_primitives_context/mineflayer.js @@ -0,0 +1,22 @@ +await bot.pathfinder.goto(goal); // A very useful function. This function may change your main-hand equipment. +// Following are some Goals you can use: +new GoalNear(x, y, z, range); // Move the bot to a block within the specified range of the specified block. `x`, `y`, `z`, and `range` are `number` +new GoalXZ(x, z); // Useful for long-range goals that don't have a specific Y level. `x` and `z` are `number` +new GoalGetToBlock(x, y, z); // Not get into the block, but get directly adjacent to it. Useful for fishing, farming, filling bucket, and beds. `x`, `y`, and `z` are `number` +new GoalFollow(entity, range); // Follow the specified entity within the specified range. `entity` is `Entity`, `range` is `number` +new GoalPlaceBlock(position, bot.world, {}); // Position the bot in order to place a block. `position` is `Vec3` +new GoalLookAtBlock(position, bot.world, {}); // Path into a position where a blockface of the block at position is visible. `position` is `Vec3` + +// These are other Mineflayer functions you can use: +bot.isABed(bedBlock); // Return true if `bedBlock` is a bed +bot.blockAt(position); // Return the block at `position`. `position` is `Vec3` + +// These are other Mineflayer async functions you can use: +await bot.equip(item, destination); // Equip the item in the specified destination. `item` is `Item`, `destination` can only be "hand", "head", "torso", "legs", "feet", "off-hand" +await bot.consume(); // Consume the item in the bot's hand. You must equip the item to consume first. Useful for eating food, drinking potions, etc. +await bot.fish(); // Let bot fish. Before calling this function, you must first get to a water block and then equip a fishing rod. The bot will automatically stop fishing when it catches a fish +await bot.sleep(bedBlock); // Sleep until sunrise. You must get to a bed block first +await bot.activateBlock(block); // This is the same as right-clicking a block in the game. Useful for buttons, doors, etc. You must get to the block first +await bot.lookAt(position); // Look at the specified position. You must go near the position before you look at it. To fill bucket with water, you must lookAt first. `position` is `Vec3` +await bot.activateItem(); // This is the same as right-clicking to use the item in the bot's hand. Useful for using buckets, etc. You must equip the item to activate first +await bot.useOn(entity); // This is the same as right-clicking an entity in the game. Useful for shearing sheep, equipping harnesses, etc. You must get to the entity first diff --git a/docs/src/voyager/control_primitives_context/placeItem.js b/docs/src/voyager/control_primitives_context/placeItem.js new file mode 100644 index 00000000..99e06089 --- /dev/null +++ b/docs/src/voyager/control_primitives_context/placeItem.js @@ -0,0 +1,28 @@ +// Place a crafting_table near the player, Vec3(1, 0, 0) is just an example, you shouldn't always use that: placeItem(bot, "crafting_table", bot.entity.position.offset(1, 0, 0)); +async function placeItem(bot, name, position) { + const item = bot.inventory.findInventoryItem(mcData.itemsByName[name].id); + // find a reference block + const faceVectors = [ + new Vec3(0, 1, 0), + new Vec3(0, -1, 0), + new Vec3(1, 0, 0), + new Vec3(-1, 0, 0), + new Vec3(0, 0, 1), + new Vec3(0, 0, -1), + ]; + let referenceBlock = null; + let faceVector = null; + for (const vector of faceVectors) { + const block = bot.blockAt(position.minus(vector)); + if (block?.name !== "air") { + referenceBlock = block; + faceVector = vector; + break; + } + } + // You must first go to the block position you want to place + await bot.pathfinder.goto(new GoalPlaceBlock(position, bot.world, {})); + // You must equip the item right before calling placeBlock + await bot.equip(item, "hand"); + await bot.placeBlock(referenceBlock, faceVector); +} diff --git a/docs/src/voyager/control_primitives_context/smeltItem.js b/docs/src/voyager/control_primitives_context/smeltItem.js new file mode 100644 index 00000000..0a3c7625 --- /dev/null +++ b/docs/src/voyager/control_primitives_context/smeltItem.js @@ -0,0 +1,22 @@ +// Smelt 1 raw_iron into 1 iron_ingot using 1 oak_planks as fuel: smeltItem(bot, "raw_iron", "oak_planks"); +// You must place a furnace before calling this function +async function smeltItem(bot, itemName, fuelName, count = 1) { + const item = mcData.itemsByName[itemName]; + const fuel = mcData.itemsByName[fuelName]; + const furnaceBlock = bot.findBlock({ + matching: mcData.blocksByName.furnace.id, + maxDistance: 32, + }); + await bot.pathfinder.goto( + new GoalLookAtBlock(furnaceBlock.position, bot.world) + ); + const furnace = await bot.openFurnace(furnaceBlock); + for (let i = 0; i < count; i++) { + await furnace.putFuel(fuel.id, null, 1); + await furnace.putInput(item.id, null, 1); + // Wait 12 seconds for the furnace to smelt the item + await bot.waitForTicks(12 * 20); + await furnace.takeOutput(); + } + await furnace.close(); +} diff --git a/docs/src/voyager/control_primitives_context/useChest.js b/docs/src/voyager/control_primitives_context/useChest.js new file mode 100644 index 00000000..e80af3fd --- /dev/null +++ b/docs/src/voyager/control_primitives_context/useChest.js @@ -0,0 +1,35 @@ +// Get a torch from chest at (30, 65, 100): getItemFromChest(bot, new Vec3(30, 65, 100), {"torch": 1}); +// This function will work no matter how far the bot is from the chest. +async function getItemFromChest(bot, chestPosition, itemsToGet) { + await moveToChest(bot, chestPosition); + const chestBlock = bot.blockAt(chestPosition); + const chest = await bot.openContainer(chestBlock); + for (const name in itemsToGet) { + const itemByName = mcData.itemsByName[name]; + const item = chest.findContainerItem(itemByName.id); + await chest.withdraw(item.type, null, itemsToGet[name]); + } + await closeChest(bot, chestBlock); +} +// Deposit a torch into chest at (30, 65, 100): depositItemIntoChest(bot, new Vec3(30, 65, 100), {"torch": 1}); +// This function will work no matter how far the bot is from the chest. +async function depositItemIntoChest(bot, chestPosition, itemsToDeposit) { + await moveToChest(bot, chestPosition); + const chestBlock = bot.blockAt(chestPosition); + const chest = await bot.openContainer(chestBlock); + for (const name in itemsToDeposit) { + const itemByName = mcData.itemsByName[name]; + const item = bot.inventory.findInventoryItem(itemByName.id); + await chest.deposit(item.type, null, itemsToDeposit[name]); + } + await closeChest(bot, chestBlock); +} +// Check the items inside the chest at (30, 65, 100): checkItemInsideChest(bot, new Vec3(30, 65, 100)); +// You only need to call this function once without any action to finish task of checking items inside the chest. +async function checkItemInsideChest(bot, chestPosition) { + await moveToChest(bot, chestPosition); + const chestBlock = bot.blockAt(chestPosition); + await bot.openContainer(chestBlock); + // You must close the chest after opening it if you are asked to open a chest + await closeChest(bot, chestBlock); +} diff --git a/docs/src/voyager/env/__init__.py b/docs/src/voyager/env/__init__.py new file mode 100644 index 00000000..24db3aef --- /dev/null +++ b/docs/src/voyager/env/__init__.py @@ -0,0 +1 @@ +from .bridge import VoyagerEnv diff --git a/docs/src/voyager/env/bridge.py b/docs/src/voyager/env/bridge.py new file mode 100644 index 00000000..188d14eb --- /dev/null +++ b/docs/src/voyager/env/bridge.py @@ -0,0 +1,189 @@ +import os.path +import time +import warnings +from typing import SupportsFloat, Any, Tuple, Dict + +import requests +import json + +import gymnasium as gym +from gymnasium.core import ObsType + +import voyager.utils as U + +from .minecraft_launcher import MinecraftInstance +from .process_monitor import SubprocessMonitor + + +class VoyagerEnv(gym.Env): + def __init__( + self, + mc_port=None, + azure_login=None, + server_host="http://127.0.0.1", + server_port=3000, + request_timeout=600, + log_path="./logs", + ): + if not mc_port and not azure_login: + raise ValueError("Either mc_port or azure_login must be specified") + if mc_port and azure_login: + warnings.warn( + "Both mc_port and mc_login are specified, mc_port will be ignored" + ) + self.mc_port = mc_port + self.azure_login = azure_login + self.server = f"{server_host}:{server_port}" + self.server_port = server_port + self.request_timeout = request_timeout + self.log_path = log_path + self.mineflayer = self.get_mineflayer_process(server_port) + if azure_login: + self.mc_instance = self.get_mc_instance() + else: + self.mc_instance = None + self.has_reset = False + self.reset_options = None + self.connected = False + self.server_paused = False + + def get_mineflayer_process(self, server_port): + U.f_mkdir(self.log_path, "mineflayer") + file_path = os.path.abspath(os.path.dirname(__file__)) + return SubprocessMonitor( + commands=[ + "node", + U.f_join(file_path, "mineflayer/index.js"), + str(server_port), + ], + name="mineflayer", + ready_match=r"Server started on port (\d+)", + log_path=U.f_join(self.log_path, "mineflayer"), + ) + + def get_mc_instance(self): + print("Creating Minecraft server") + U.f_mkdir(self.log_path, "minecraft") + return MinecraftInstance( + **self.azure_login, + mineflayer=self.mineflayer, + log_path=U.f_join(self.log_path, "minecraft"), + ) + + def check_process(self): + if self.mc_instance and not self.mc_instance.is_running: + # if self.mc_instance: + # self.mc_instance.check_process() + # if not self.mc_instance.is_running: + print("Starting Minecraft server") + self.mc_instance.run() + self.mc_port = self.mc_instance.port + self.reset_options["port"] = self.mc_instance.port + print(f"Server started on port {self.reset_options['port']}") + retry = 0 + while not self.mineflayer.is_running: + print("Mineflayer process has exited, restarting") + self.mineflayer.run() + if not self.mineflayer.is_running: + if retry > 3: + raise RuntimeError("Mineflayer process failed to start") + else: + continue + print(self.mineflayer.ready_line) + res = requests.post( + f"{self.server}/start", + json=self.reset_options, + timeout=self.request_timeout, + ) + if res.status_code != 200: + self.mineflayer.stop() + raise RuntimeError( + f"Minecraft server reply with code {res.status_code}" + ) + return res.json() + + def step( + self, + code: str, + programs: str = "", + ) -> Tuple[ObsType, SupportsFloat, bool, bool, Dict[str, Any]]: + if not self.has_reset: + raise RuntimeError("Environment has not been reset yet") + self.check_process() + self.unpause() + data = { + "code": code, + "programs": programs, + } + res = requests.post( + f"{self.server}/step", json=data, timeout=self.request_timeout + ) + if res.status_code != 200: + raise RuntimeError("Failed to step Minecraft server") + returned_data = res.json() + self.pause() + return json.loads(returned_data) + + def render(self): + raise NotImplementedError("render is not implemented") + + def reset( + self, + *, + seed=None, + options=None, + ) -> Tuple[ObsType, Dict[str, Any]]: + if options is None: + options = {} + + if options.get("inventory", {}) and options.get("mode", "hard") != "hard": + raise RuntimeError("inventory can only be set when options is hard") + + self.reset_options = { + "port": self.mc_port, + "reset": options.get("mode", "hard"), + "inventory": options.get("inventory", {}), + "equipment": options.get("equipment", []), + "spread": options.get("spread", False), + "waitTicks": options.get("wait_ticks", 5), + "position": options.get("position", None), + } + + self.unpause() + self.mineflayer.stop() + time.sleep(1) # wait for mineflayer to exit + + returned_data = self.check_process() + self.has_reset = True + self.connected = True + # All the reset in step will be soft + self.reset_options["reset"] = "soft" + self.pause() + return json.loads(returned_data) + + def close(self): + self.unpause() + if self.connected: + res = requests.post(f"{self.server}/stop") + if res.status_code == 200: + self.connected = False + if self.mc_instance: + self.mc_instance.stop() + self.mineflayer.stop() + return not self.connected + + def pause(self): + if self.mineflayer.is_running and not self.server_paused: + res = requests.post(f"{self.server}/pause") + if res.status_code == 200: + self.server_paused = True + return self.server_paused + + def unpause(self): + if self.mineflayer.is_running and self.server_paused: + res = requests.post(f"{self.server}/pause") + if res.status_code == 200: + self.server_paused = False + else: + print(res.json()) + return self.server_paused diff --git a/docs/src/voyager/env/minecraft_launcher.py b/docs/src/voyager/env/minecraft_launcher.py new file mode 100644 index 00000000..928e887c --- /dev/null +++ b/docs/src/voyager/env/minecraft_launcher.py @@ -0,0 +1,127 @@ +import os +import re + +import minecraft_launcher_lib +import sys +import voyager.utils as U + +from .process_monitor import SubprocessMonitor + + +class MinecraftInstance: + def __init__( + self, + client_id, + redirect_url, + secret_value, + version, + mineflayer, + log_path="logs", + ): + self.client_id = client_id + self.redirect_url = redirect_url + self.secret_value = secret_value + self.version = version + self.log_path = log_path + self.mc_dir = minecraft_launcher_lib.utils.get_minecraft_directory() + self.port = None + + def stop_mineflayer(): + print("Stopping mineflayer") + try: + mineflayer.stop() + except Exception as e: + print(e) + + self.mc_command = self.get_mc_command() + self.mc_process = SubprocessMonitor( + commands=self.mc_command, + name="minecraft", + ready_match=r"Started serving on (\d+)", + log_path=self.log_path, + callback=stop_mineflayer, + callback_match=r"\[Server thread/INFO\]: bot left the game", + finished_callback=stop_mineflayer, + ) + + def get_mineflayer_process(self, server_port): + U.f_mkdir(self.log_path, "../mineflayer") + file_path = os.path.abspath(os.path.dirname(__file__)) + return SubprocessMonitor( + commands=[ + "node", + U.f_join(file_path, "mineflayer/index.js"), + str(server_port), + ], + name="mineflayer", + ready_match=r"Server started on port (\d+)", + log_path=U.f_join(self.log_path, "mineflayer"), + ) + + def get_mc_command(self): + file_path = os.path.abspath(os.path.dirname(__file__)) + if not U.f_exists(file_path, "config.json"): + ( + login_url, + state, + code_verifier, + ) = minecraft_launcher_lib.microsoft_account.get_secure_login_data( + self.client_id, self.redirect_url + ) + print( + f"Please open {login_url} in your browser and copy the url you are redirected into the prompt below." + ) + code_url = input() + + try: + auth_code = ( + minecraft_launcher_lib.microsoft_account.parse_auth_code_url( + code_url, state + ) + ) + except AssertionError: + print("States do not match!") + sys.exit(1) + except KeyError: + print("Url not valid") + sys.exit(1) + + login_data = minecraft_launcher_lib.microsoft_account.complete_login( + self.client_id, + self.secret_value, + self.redirect_url, + auth_code, + code_verifier, + ) + + options = { + "username": login_data["name"], + "uuid": login_data["id"], + "token": login_data["access_token"], + } + U.json_dump(options, file_path, "config.json") + print(f"Login success, save to {U.f_join(file_path, 'config.json')}") + + options = U.json_load(file_path, "config.json") + mc_command = minecraft_launcher_lib.command.get_minecraft_command( + self.version, self.mc_dir, options + ) + + return mc_command + + def run(self): + self.mc_process.run() + pattern = r"Started serving on (\d+)" + match = re.search(pattern, self.mc_process.ready_line) + if match: + self.port = int(match.group(1)) + print("The mc server is listening on port", self.port) + else: + raise RuntimeError("Port not found") + + def stop(self): + self.mc_process.stop() + + @property + def is_running(self): + return self.mc_process.is_running diff --git a/docs/src/voyager/env/process_monitor.py b/docs/src/voyager/env/process_monitor.py new file mode 100644 index 00000000..2ae32370 --- /dev/null +++ b/docs/src/voyager/env/process_monitor.py @@ -0,0 +1,90 @@ +import time +import re +import warnings +from typing import List + +import psutil +import subprocess +import logging +import threading + +import voyager.utils as U + + +class SubprocessMonitor: + def __init__( + self, + commands: List[str], + name: str, + ready_match: str = r".*", + log_path: str = "logs", + callback_match: str = r"^(?!x)x$", # regex that will never match + callback: callable = None, + finished_callback: callable = None, + ): + self.commands = commands + start_time = time.strftime("%Y%m%d_%H%M%S") + self.name = name + self.logger = logging.getLogger(name) + handler = logging.FileHandler(U.f_join(log_path, f"{start_time}.log")) + formatter = logging.Formatter( + "%(asctime)s - %(name)s - %(levelname)s - %(message)s" + ) + handler.setFormatter(formatter) + self.logger.addHandler(handler) + self.logger.setLevel(logging.INFO) + self.process = None + self.ready_match = ready_match + self.ready_event = None + self.ready_line = None + self.callback_match = callback_match + self.callback = callback + self.finished_callback = finished_callback + self.thread = None + + def _start(self): + self.logger.info(f"Starting subprocess with commands: {self.commands}") + + self.process = psutil.Popen( + self.commands, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + universal_newlines=True, + ) + print(f"Subprocess {self.name} started with PID {self.process.pid}.") + for line in iter(self.process.stdout.readline, ""): + self.logger.info(line.strip()) + if re.search(self.ready_match, line): + self.ready_line = line + self.logger.info("Subprocess is ready.") + self.ready_event.set() + if re.search(self.callback_match, line): + self.callback() + if not self.ready_event.is_set(): + self.ready_event.set() + warnings.warn(f"Subprocess {self.name} failed to start.") + if self.finished_callback: + self.finished_callback() + + def run(self): + self.ready_event = threading.Event() + self.ready_line = None + self.thread = threading.Thread(target=self._start) + self.thread.start() + self.ready_event.wait() + + def stop(self): + self.logger.info("Stopping subprocess.") + if self.process and self.process.is_running(): + self.process.terminate() + self.process.wait() + + # def __del__(self): + # if self.process.is_running(): + # self.stop() + + @property + def is_running(self): + if self.process is None: + return False + return self.process.is_running() diff --git a/docs/src/voyager/prompts/__init__.py b/docs/src/voyager/prompts/__init__.py new file mode 100644 index 00000000..bd9964f8 --- /dev/null +++ b/docs/src/voyager/prompts/__init__.py @@ -0,0 +1,7 @@ +import pkg_resources +import voyager.utils as U + + +def load_prompt(prompt): + package_path = pkg_resources.resource_filename("voyager", "") + return U.load_text(f"{package_path}/prompts/{prompt}.txt") diff --git a/docs/src/voyager/prompts/action_response_format.txt b/docs/src/voyager/prompts/action_response_format.txt new file mode 100644 index 00000000..df3713a8 --- /dev/null +++ b/docs/src/voyager/prompts/action_response_format.txt @@ -0,0 +1,15 @@ +Explain: ... +Plan: +1) ... +2) ... +3) ... +... +Code: +```javascript +// helper functions (only if needed, try to avoid them) +... +// main function after the helper functions +async function yourMainFunctionName(bot) { + // ... +} +``` \ No newline at end of file diff --git a/docs/src/voyager/prompts/action_template.txt b/docs/src/voyager/prompts/action_template.txt new file mode 100644 index 00000000..d6061f0a --- /dev/null +++ b/docs/src/voyager/prompts/action_template.txt @@ -0,0 +1,49 @@ +You are a helpful assistant that writes Mineflayer javascript code to complete any Minecraft task specified by me. + +Here are some useful programs written with Mineflayer APIs. + +{programs} + + +At each round of conversation, I will give you +Code from the last round: ... +Execution error: ... +Chat log: ... +Biome: ... +Time: ... +Nearby blocks: ... +Nearby entities (nearest to farthest): +Health: ... +Hunger: ... +Position: ... +Equipment: ... +Inventory (xx/36): ... +Chests: ... +Task: ... +Context: ... +Critique: ... + +You should then respond to me with +Explain (if applicable): Are there any steps missing in your plan? Why does the code not complete the task? What does the chat log and execution error imply? +Plan: How to complete the task step by step. You should pay attention to Inventory since it tells what you have. The task completeness check is also based on your final inventory. +Code: + 1) Write an async function taking the bot as the only argument. + 2) Reuse the above useful programs as much as possible. + - Use `mineBlock(bot, name, count)` to collect blocks. Do not use `bot.dig` directly. + - Use `craftItem(bot, name, count)` to craft items. Do not use `bot.craft` or `bot.recipesFor` directly. + - Use `smeltItem(bot, name count)` to smelt items. Do not use `bot.openFurnace` directly. + - Use `placeItem(bot, name, position)` to place blocks. Do not use `bot.placeBlock` directly. + - Use `killMob(bot, name, timeout)` to kill mobs. Do not use `bot.attack` directly. + 3) Your function will be reused for building more complex functions. Therefore, you should make it generic and reusable. You should not make strong assumption about the inventory (as it may be changed at a later time), and therefore you should always check whether you have the required items before using them. If not, you should first collect the required items and reuse the above useful programs. + 4) Functions in the "Code from the last round" section will not be saved or executed. Do not reuse functions listed there. + 5) Anything defined outside a function will be ignored, define all your variables inside your functions. + 6) Call `bot.chat` to show the intermediate progress. + 7) Use `exploreUntil(bot, direction, maxDistance, callback)` when you cannot find something. You should frequently call this before mining blocks or killing mobs. You should select a direction at random every time instead of constantly using (1, 0, 1). + 8) `maxDistance` should always be 32 for `bot.findBlocks` and `bot.findBlock`. Do not cheat. + 9) Do not write infinite loops or recursive functions. + 10) Do not use `bot.on` or `bot.once` to register event listeners. You definitely do not need them. + 11) Name your function in a meaningful way (can infer the task from the name). + +You should only respond in the format as described below: +RESPONSE FORMAT: +{response_format} diff --git a/docs/src/voyager/prompts/critic.txt b/docs/src/voyager/prompts/critic.txt new file mode 100644 index 00000000..c4295067 --- /dev/null +++ b/docs/src/voyager/prompts/critic.txt @@ -0,0 +1,127 @@ +You are an assistant that assesses my progress of playing Minecraft and provides useful guidance. + +You are required to evaluate if I have met the task requirements. Exceeding the task requirements is also considered a success while failing to meet them requires you to provide critique to help me improve. + +I will give you the following information: + +Biome: The biome after the task execution. +Time: The current time. +Nearby blocks: The surrounding blocks. These blocks are not collected yet. However, this is useful for some placing or planting tasks. +Health: My current health. +Hunger: My current hunger level. For eating task, if my hunger level is 20.0, then I successfully ate the food. +Position: My current position. +Equipment: My final equipment. For crafting tasks, I sometimes equip the crafted item. +Inventory (xx/36): My final inventory. For mining and smelting tasks, you only need to check inventory. +Chests: If the task requires me to place items in a chest, you can find chest information here. +Task: The objective I need to accomplish. +Context: The context of the task. + +You should only respond in JSON format as described below: +{ + "reasoning": "reasoning", + "success": boolean, + "critique": "critique", +} +Ensure the response can be parsed by Python `json.loads`, e.g.: no trailing commas, no single quotes, etc. + +Here are some examples: +INPUT: +Inventory (2/36): {'oak_log':2, 'spruce_log':2} + +Task: Mine 3 wood logs + +RESPONSE: +{ + "reasoning": "You need to mine 3 wood logs. You have 2 oak logs and 2 spruce logs, which add up to 4 wood logs.", + "success": true, + "critique": "" +} + +INPUT: +Inventory (3/36): {'crafting_table': 1, 'spruce_planks': 6, 'stick': 4} + +Task: Craft a wooden pickaxe + +RESPONSE: +{ + "reasoning": "You have enough materials to craft a wooden pickaxe, but you didn't craft it.", + "success": false, + "critique": "Craft a wooden pickaxe with a crafting table using 3 spruce planks and 2 sticks." +} + +INPUT: +Inventory (2/36): {'raw_iron': 5, 'stone_pickaxe': 1} + +Task: Mine 5 iron_ore + +RESPONSE: +{ + "reasoning": "Mining iron_ore in Minecraft will get raw_iron. You have 5 raw_iron in your inventory.", + "success": true, + "critique": "" +} + +INPUT: +Biome: plains + +Nearby blocks: stone, dirt, grass_block, grass, farmland, wheat + +Inventory (26/36): ... + +Task: Plant 1 wheat seed. + +RESPONSE: +{ + "reasoning": "For planting tasks, inventory information is useless. In nearby blocks, there is farmland and wheat, which means you succeed to plant the wheat seed.", + "success": true, + "critique": "" +} + +INPUT: +Inventory (11/36): {... ,'rotten_flesh': 1} + +Task: Kill 1 zombie + +Context: ... + +RESPONSE +{ + "reasoning": "You have rotten flesh in your inventory, which means you successfully killed one zombie.", + "success": true, + "critique": "" +} + +INPUT: +Hunger: 20.0/20.0 + +Inventory (11/36): ... + +Task: Eat 1 ... + +Context: ... + +RESPONSE +{ + "reasoning": "For all eating task, if the player's hunger is 20.0, then the player successfully ate the food.", + "success": true, + "critique": "" +} + +INPUT: +Nearby blocks: chest + +Inventory (28/36): {'rail': 1, 'coal': 2, 'oak_planks': 13, 'copper_block': 1, 'diorite': 7, 'cooked_beef': 4, 'granite': 22, 'cobbled_deepslate': 23, 'feather': 4, 'leather': 2, 'cooked_chicken': 3, 'white_wool': 2, 'stick': 3, 'black_wool': 1, 'stone_sword': 2, 'stone_hoe': 1, 'stone_axe': 2, 'stone_shovel': 2, 'cooked_mutton': 4, 'cobblestone_wall': 18, 'crafting_table': 1, 'furnace': 1, 'iron_pickaxe': 1, 'stone_pickaxe': 1, 'raw_copper': 12} + +Chests: +(81, 131, 16): {'andesite': 2, 'dirt': 2, 'cobblestone': 75, 'wooden_pickaxe': 1, 'wooden_sword': 1} + +Task: Deposit useless items into the chest at (81, 131, 16) + +Context: ... + +RESPONSE +{ + "reasoning": "You have 28 items in your inventory after depositing, which is more than 20. You need to deposit more items from your inventory to the chest.", + "success": false, + "critique": "Deposit more useless items such as copper_block, diorite, granite, cobbled_deepslate, feather, and leather to meet the requirement of having only 20 occupied slots in your inventory." +} \ No newline at end of file diff --git a/docs/src/voyager/prompts/curriculum.txt b/docs/src/voyager/prompts/curriculum.txt new file mode 100644 index 00000000..66a33c62 --- /dev/null +++ b/docs/src/voyager/prompts/curriculum.txt @@ -0,0 +1,42 @@ +You are a helpful assistant that tells me the next immediate task to do in Minecraft. My ultimate goal is to discover as many diverse things as possible, accomplish as many diverse tasks as possible and become the best Minecraft player in the world. + +I will give you the following information: +Question 1: ... +Answer: ... +Question 2: ... +Answer: ... +Question 3: ... +Answer: ... +... +Biome: ... +Time: ... +Nearby blocks: ... +Other blocks that are recently seen: ... +Nearby entities (nearest to farthest): ... +Health: Higher than 15 means I'm healthy. +Hunger: Higher than 15 means I'm not hungry. +Position: ... +Equipment: If I have better armor in my inventory, you should ask me to equip it. +Inventory (xx/36): ... +Chests: You can ask me to deposit or take items from these chests. There also might be some unknown chest, you should ask me to open and check items inside the unknown chest. +Completed tasks so far: ... +Failed tasks that are too hard: ... + +You must follow the following criteria: +1) You should act as a mentor and guide me to the next task based on my current learning progress. +2) Please be very specific about what resources I need to collect, what I need to craft, or what mobs I need to kill. +3) The next task should follow a concise format, such as "Mine [quantity] [block]", "Craft [quantity] [item]", "Smelt [quantity] [item]", "Kill [quantity] [mob]", "Cook [quantity] [food]", "Equip [item]" etc. It should be a single phrase. Do not propose multiple tasks at the same time. Do not mention anything else. +4) The next task should not be too hard since I may not have the necessary resources or have learned enough skills to complete it yet. +5) The next task should be novel and interesting. I should look for rare resources, upgrade my equipment and tools using better materials, and discover new things. I should not be doing the same thing over and over again. +6) I may sometimes need to repeat some tasks if I need to collect more resources to complete more difficult tasks. Only repeat tasks if necessary. +7) Do not ask me to build or dig shelter even if it's at night. I want to explore the world and discover new things. I don't want to stay in one place. +8) Tasks that require information beyond the player's status to verify should be avoided. For instance, "Placing 4 torches" and "Dig a 2x1x2 hole" are not ideal since they require visual confirmation from the screen. All the placing, building, planting, and trading tasks should be avoided. Do not propose task starting with these keywords. + +You should only respond in the format as described below: +RESPONSE FORMAT: +Reasoning: Based on the information I listed above, do reasoning about what the next task should be. +Task: The next task. + +Here's an example response: +Reasoning: The inventory is empty now, chop down a tree to get some wood. +Task: Obtain a wood log. \ No newline at end of file diff --git a/docs/src/voyager/prompts/curriculum_qa_step1_ask_questions.txt b/docs/src/voyager/prompts/curriculum_qa_step1_ask_questions.txt new file mode 100644 index 00000000..6d93537f --- /dev/null +++ b/docs/src/voyager/prompts/curriculum_qa_step1_ask_questions.txt @@ -0,0 +1,94 @@ +You are a helpful assistant that asks questions to help me decide the next immediate task to do in Minecraft. My ultimate goal is to discover as many things as possible, accomplish as many tasks as possible and become the best Minecraft player in the world. + +I will give you the following information: +Biome: ... +Time: ... +Nearby blocks: ... +Other blocks that are recently seen: ... +Nearby entities (nearest to farthest): ... +Health: ... +Hunger: ... +Position: ... +Equipment: ... +Inventory (xx/36): ... +Chests: ... +Completed tasks so far: ... +Failed tasks that are too hard: ... + +You must follow the following criteria: +1) You should ask at least 5 questions (but no more than 10 questions) to help me decide the next immediate task to do. Each question should be followed by the concept that the question is about. +2) Your question should be specific to a concept in Minecraft. + Bad example (the question is too general): + Question: What is the best way to play Minecraft? + Concept: unknown + Bad example (axe is still general, you should specify the type of axe such as wooden axe): + What are the benefits of using an axe to gather resources? + Concept: axe + Good example: + Question: How to make a wooden pickaxe? + Concept: wooden pickaxe +3) Your questions should be self-contained and not require any context. + Bad example (the question requires the context of my current biome): + Question: What are the blocks that I can find in my current biome? + Concept: unknown + Bad example (the question requires the context of my current inventory): + Question: What are the resources you need the most currently? + Concept: unknown + Bad example (the question requires the context of my current inventory): + Question: Do you have any gold or emerald resources? + Concept: gold + Bad example (the question requires the context of my nearby entities): + Question: Can you see any animals nearby that you can kill for food? + Concept: food + Bad example (the question requires the context of my nearby blocks): + Question: Is there any water source nearby? + Concept: water + Good example: + Question: What are the blocks that I can find in the sparse jungle? + Concept: sparse jungle +4) Do not ask questions about building tasks (such as building a shelter) since they are too hard for me to do. + +Let's say your current biome is sparse jungle. You can ask questions like: +Question: What are the items that I can find in the sparse jungle? +Concept: sparse jungle +Question: What are the mobs that I can find in the sparse jungle? +Concept: sparse jungle + +Let's say you see a creeper nearby, and you have not defeated a creeper before. You can ask a question like: +Question: How to defeat the creeper? +Concept: creeper + +Let's say your last completed task is "Craft a wooden pickaxe". You can ask a question like: +Question: What are the suggested tasks that I can do after crafting a wooden pickaxe? +Concept: wooden pickaxe + +Here are some more question and concept examples: +Question: What are the ores that I can find in the sparse jungle? +Concept: sparse jungle +(the above concept should not be "ore" because I need to look up the page of "sparse jungle" to find out what ores I can find in the sparse jungle) +Question: How can you obtain food in the sparse jungle? +Concept: sparse jungle +(the above concept should not be "food" because I need to look up the page of "sparse jungle" to find out what food I can obtain in the sparse jungle) +Question: How can you use the furnace to upgrade your equipment and make useful items? +Concept: furnace +Question: How to obtain a diamond ore? +Concept: diamond ore +Question: What are the benefits of using a stone pickaxe over a wooden pickaxe? +Concept: stone pickaxe +Question: What are the tools that you can craft using wood planks and sticks? +Concept: wood planks + +You should only respond in the format as described below: +RESPONSE FORMAT: +Reasoning: ... +Question 1: ... +Concept 1: ... +Question 2: ... +Concept 2: ... +Question 3: ... +Concept 3: ... +Question 4: ... +Concept 4: ... +Question 5: ... +Concept 5: ... +... diff --git a/docs/src/voyager/prompts/curriculum_qa_step2_answer_questions.txt b/docs/src/voyager/prompts/curriculum_qa_step2_answer_questions.txt new file mode 100644 index 00000000..860d986e --- /dev/null +++ b/docs/src/voyager/prompts/curriculum_qa_step2_answer_questions.txt @@ -0,0 +1,8 @@ +You are a helpful assistant that answer my question about Minecraft. + +I will give you the following information: +Question: ... + +You will answer the question based on the context (only if available and helpful) and your own knowledge of Minecraft. +1) Start your answer with "Answer: ". +2) Answer "Answer: Unknown" if you don't know the answer. \ No newline at end of file diff --git a/docs/src/voyager/prompts/curriculum_task_decomposition.txt b/docs/src/voyager/prompts/curriculum_task_decomposition.txt new file mode 100644 index 00000000..0e3b1592 --- /dev/null +++ b/docs/src/voyager/prompts/curriculum_task_decomposition.txt @@ -0,0 +1,12 @@ +You are a helpful assistant that generates a curriculum of subgoals to complete any Minecraft task specified by me. + +I'll give you a final task and my current inventory, you need to decompose the task into a list of subgoals based on my inventory. + +You must follow the following criteria: +1) Return a Python list of subgoals that can be completed in order to complete the specified task. +2) Each subgoal should follow a concise format, such as "Mine [quantity] [block]", "Craft [quantity] [item]", "Smelt [quantity] [item]", "Kill [quantity] [mob]", "Cook [quantity] [food]", "Equip [item]". +3) Include each level of necessary tools as a subgoal, such as wooden, stone, iron, diamond, etc. + +You should only respond in JSON format as described below: +["subgoal1", "subgoal2", "subgoal3", ...] +Ensure the response can be parsed by Python `json.loads`, e.g.: no trailing commas, no single quotes, etc. \ No newline at end of file diff --git a/docs/src/voyager/prompts/skill.txt b/docs/src/voyager/prompts/skill.txt new file mode 100644 index 00000000..dc846cdf --- /dev/null +++ b/docs/src/voyager/prompts/skill.txt @@ -0,0 +1,51 @@ +You are a helpful assistant that writes a description of the given function written in Mineflayer javascript code. + +1) Do not mention the function name. +2) Do not mention anything about `bot.chat` or helper functions. +3) There might be some helper functions before the main function, but you only need to describe the main function. +4) Try to summarize the function in no more than 6 sentences. +5) Your response should be a single line of text. + +For example, if the function is: + +async function mineCobblestone(bot) { + // Check if the wooden pickaxe is in the inventory, if not, craft one + let woodenPickaxe = bot.inventory.findInventoryItem(mcData.itemsByName["wooden_pickaxe"].id); + if (!woodenPickaxe) { + bot.chat("Crafting a wooden pickaxe."); + await craftWoodenPickaxe(bot); + woodenPickaxe = bot.inventory.findInventoryItem(mcData.itemsByName["wooden_pickaxe"].id); + } + + // Equip the wooden pickaxe if it exists + if (woodenPickaxe) { + await bot.equip(woodenPickaxe, "hand"); + + // Explore until we find a stone block + await exploreUntil(bot, new Vec3(1, -1, 1), 60, () => { + const stone = bot.findBlock({ + matching: mcData.blocksByName["stone"].id, + maxDistance: 32 + }); + if (stone) { + return true; + } + }); + + // Mine 8 cobblestone blocks using the wooden pickaxe + bot.chat("Found a stone block. Mining 8 cobblestone blocks."); + await mineBlock(bot, "stone", 8); + bot.chat("Successfully mined 8 cobblestone blocks."); + + // Save the event of mining 8 cobblestone + bot.save("cobblestone_mined"); + } else { + bot.chat("Failed to craft a wooden pickaxe. Cannot mine cobblestone."); + } +} + +The main function is `mineCobblestone`. + +Then you would write: + +The function is about mining 8 cobblestones using a wooden pickaxe. First check if a wooden pickaxe is in the inventory. If not, craft one. If the wooden pickaxe is available, equip the wooden pickaxe in the hand. Next, explore the environment until finding a stone block. Once a stone block is found, mine a total of 8 cobblestone blocks using the wooden pickaxe. \ No newline at end of file diff --git a/docs/src/voyager/utils/__init__.py b/docs/src/voyager/utils/__init__.py new file mode 100644 index 00000000..8687afef --- /dev/null +++ b/docs/src/voyager/utils/__init__.py @@ -0,0 +1,3 @@ +from .file_utils import * +from .json_utils import * +from .record_utils import EventRecorder diff --git a/docs/src/voyager/utils/file_utils.py b/docs/src/voyager/utils/file_utils.py new file mode 100644 index 00000000..9fe8ff44 --- /dev/null +++ b/docs/src/voyager/utils/file_utils.py @@ -0,0 +1,568 @@ +""" +File system utils. +""" +import collections +import os +import pickle +import sys +import errno +import shutil +import glob + +# import pwd +import codecs +import hashlib +import tarfile +import fnmatch +import tempfile +from datetime import datetime +from socket import gethostname +import logging + + +f_ext = os.path.splitext + +f_size = os.path.getsize + +is_file = os.path.isfile + +is_dir = os.path.isdir + +get_dir = os.path.dirname + + +def host_name(): + "Get host name, alias with ``socket.gethostname()``" + return gethostname() + + +def host_id(): + """ + Returns: first part of hostname up to '.' + """ + return host_name().split(".")[0] + + +def utf_open(fname, mode): + """ + Wrapper for codecs.open + """ + return codecs.open(fname, mode=mode, encoding="utf-8") + + +def is_sequence(obj): + """ + Returns: + True if the sequence is a collections.Sequence and not a string. + """ + return isinstance(obj, collections.abc.Sequence) and not isinstance(obj, str) + + +def pack_varargs(args): + """ + Pack *args or a single list arg as list + + def f(*args): + arg_list = pack_varargs(args) + # arg_list is now packed as a list + """ + assert isinstance(args, tuple), "please input the tuple `args` as in *args" + if len(args) == 1 and is_sequence(args[0]): + return args[0] + else: + return args + + +def f_not_empty(*fpaths): + """ + Returns: + True if and only if the file exists and file size > 0 + if fpath is a dir, if and only if dir exists and has at least 1 file + """ + fpath = f_join(*fpaths) + if not os.path.exists(fpath): + return False + + if os.path.isdir(fpath): + return len(os.listdir(fpath)) > 0 + else: + return os.path.getsize(fpath) > 0 + + +def f_expand(fpath): + return os.path.expandvars(os.path.expanduser(fpath)) + + +def f_exists(*fpaths): + return os.path.exists(f_join(*fpaths)) + + +def f_join(*fpaths): + """ + join file paths and expand special symbols like `~` for home dir + """ + fpaths = pack_varargs(fpaths) + fpath = f_expand(os.path.join(*fpaths)) + if isinstance(fpath, str): + fpath = fpath.strip() + return fpath + + +def f_listdir( + *fpaths, + filter_ext=None, + filter=None, + sort=True, + full_path=False, + nonexist_ok=True, + recursive=False, +): + """ + Args: + full_path: True to return full paths to the dir contents + filter: function that takes in file name and returns True to include + nonexist_ok: True to return [] if the dir is non-existent, False to raise + sort: sort the file names by alphabetical + recursive: True to use os.walk to recursively list files. Note that `filter` + will be applied to the relative path string to the root dir. + e.g. filter will take "a/data1.txt" and "a/b/data3.txt" as input, instead of + just the base file names "data1.txt" and "data3.txt". + if False, will simply call os.listdir() + """ + assert not (filter_ext and filter), "filter_ext and filter are mutually exclusive" + dir_path = f_join(*fpaths) + if not os.path.exists(dir_path) and nonexist_ok: + return [] + if recursive: + files = [ + os.path.join(os.path.relpath(root, dir_path), file) + for root, _, files in os.walk(dir_path) + for file in files + ] + else: + files = os.listdir(dir_path) + if filter is not None: + files = [f for f in files if filter(f)] + elif filter_ext is not None: + files = [f for f in files if f.endswith(filter_ext)] + if sort: + files.sort() + if full_path: + return [os.path.join(dir_path, f) for f in files] + else: + return files + + +def f_mkdir(*fpaths): + """ + Recursively creates all the subdirs + If exist, do nothing. + """ + fpath = f_join(*fpaths) + os.makedirs(fpath, exist_ok=True) + return fpath + + +def f_mkdir_in_path(*fpaths): + """ + fpath is a file, + recursively creates all the parent dirs that lead to the file + If exist, do nothing. + """ + os.makedirs(get_dir(f_join(*fpaths)), exist_ok=True) + + +def last_part_in_path(fpath): + """ + https://stackoverflow.com/questions/3925096/how-to-get-only-the-last-part-of-a-path-in-python + """ + return os.path.basename(os.path.normpath(f_expand(fpath))) + + +def is_abs_path(*fpath): + return os.path.isabs(f_join(*fpath)) + + +def is_relative_path(*fpath): + return not is_abs_path(f_join(*fpath)) + + +def f_time(*fpath): + "File modification time" + return str(os.path.getctime(f_join(*fpath))) + + +def f_append_before_ext(fpath, suffix): + """ + Append a suffix to file name and retain its extension + """ + name, ext = f_ext(fpath) + return name + suffix + ext + + +def f_add_ext(fpath, ext): + """ + Append an extension if not already there + Args: + ext: will add a preceding `.` if doesn't exist + """ + if not ext.startswith("."): + ext = "." + ext + if fpath.endswith(ext): + return fpath + else: + return fpath + ext + + +def f_has_ext(fpath, ext): + "Test if file path is a text file" + _, actual_ext = f_ext(fpath) + return actual_ext == "." + ext.lstrip(".") + + +def f_glob(*fpath): + return glob.glob(f_join(*fpath), recursive=True) + + +def f_remove(*fpath, verbose=False, dry_run=False): + """ + If exist, remove. Supports both dir and file. Supports glob wildcard. + """ + assert isinstance(verbose, bool) + fpath = f_join(fpath) + if dry_run: + print("Dry run, delete:", fpath) + return + for f in glob.glob(fpath): + try: + shutil.rmtree(f) + except OSError as e: + if e.errno == errno.ENOTDIR: + try: + os.remove(f) + except: # final resort safeguard + pass + if verbose: + print(f'Deleted "{fpath}"') + + +def f_copy(fsrc, fdst, ignore=None, include=None, exists_ok=True, verbose=False): + """ + Supports both dir and file. Supports glob wildcard. + """ + fsrc, fdst = f_expand(fsrc), f_expand(fdst) + for f in glob.glob(fsrc): + try: + f_copytree(f, fdst, ignore=ignore, include=include, exist_ok=exists_ok) + except OSError as e: + if e.errno == errno.ENOTDIR: + shutil.copy(f, fdst) + else: + raise + if verbose: + print(f'Copied "{fsrc}" to "{fdst}"') + + +def _f_copytree( + src, + dst, + symlinks=False, + ignore=None, + exist_ok=True, + copy_function=shutil.copy2, + ignore_dangling_symlinks=False, +): + """Copied from python standard lib shutil.copytree + except that we allow exist_ok + Use f_copytree as entry + """ + names = os.listdir(src) + if ignore is not None: + ignored_names = ignore(src, names) + else: + ignored_names = set() + + os.makedirs(dst, exist_ok=exist_ok) + errors = [] + for name in names: + if name in ignored_names: + continue + srcname = os.path.join(src, name) + dstname = os.path.join(dst, name) + try: + if os.path.islink(srcname): + linkto = os.readlink(srcname) + if symlinks: + # We can't just leave it to `copy_function` because legacy + # code with a custom `copy_function` may rely on copytree + # doing the right thing. + os.symlink(linkto, dstname) + shutil.copystat(srcname, dstname, follow_symlinks=not symlinks) + else: + # ignore dangling symlink if the flag is on + if not os.path.exists(linkto) and ignore_dangling_symlinks: + continue + # otherwise let the copy occurs. copy2 will raise an error + if os.path.isdir(srcname): + _f_copytree( + srcname, dstname, symlinks, ignore, exist_ok, copy_function + ) + else: + copy_function(srcname, dstname) + elif os.path.isdir(srcname): + _f_copytree(srcname, dstname, symlinks, ignore, exist_ok, copy_function) + else: + # Will raise a SpecialFileError for unsupported file types + copy_function(srcname, dstname) + # catch the Error from the recursive copytree so that we can + # continue with other files + except shutil.Error as err: + errors.extend(err.args[0]) + except OSError as why: + errors.append((srcname, dstname, str(why))) + try: + shutil.copystat(src, dst) + except OSError as why: + # Copying file access times may fail on Windows + if getattr(why, "winerror", None) is None: + errors.append((src, dst, str(why))) + if errors: + raise shutil.Error(errors) + return dst + + +def _include_patterns(*patterns): + """Factory function that can be used with copytree() ignore parameter. + + Arguments define a sequence of glob-style patterns + that are used to specify what files to NOT ignore. + Creates and returns a function that determines this for each directory + in the file hierarchy rooted at the source directory when used with + shutil.copytree(). + """ + + def _ignore_patterns(path, names): + keep = set( + name for pattern in patterns for name in fnmatch.filter(names, pattern) + ) + ignore = set( + name + for name in names + if name not in keep and not os.path.isdir(os.path.join(path, name)) + ) + return ignore + + return _ignore_patterns + + +def f_copytree(fsrc, fdst, symlinks=False, ignore=None, include=None, exist_ok=True): + fsrc, fdst = f_expand(fsrc), f_expand(fdst) + assert (ignore is None) or ( + include is None + ), "ignore= and include= are mutually exclusive" + if ignore: + ignore = shutil.ignore_patterns(*ignore) + elif include: + ignore = _include_patterns(*include) + _f_copytree(fsrc, fdst, ignore=ignore, symlinks=symlinks, exist_ok=exist_ok) + + +def f_move(fsrc, fdst): + fsrc, fdst = f_expand(fsrc), f_expand(fdst) + for f in glob.glob(fsrc): + shutil.move(f, fdst) + + +def f_split_path(fpath, normpath=True): + """ + Splits path into a list of its component folders + + Args: + normpath: call os.path.normpath to remove redundant '/' and + up-level references like ".." + """ + if normpath: + fpath = os.path.normpath(fpath) + allparts = [] + while 1: + parts = os.path.split(fpath) + if parts[0] == fpath: # sentinel for absolute paths + allparts.insert(0, parts[0]) + break + elif parts[1] == fpath: # sentinel for relative paths + allparts.insert(0, parts[1]) + break + else: + fpath = parts[0] + allparts.insert(0, parts[1]) + return allparts + + +def get_script_dir(): + """ + Returns: the dir of current script + """ + return os.path.dirname(os.path.realpath(sys.argv[0])) + + +def get_script_file_name(): + """ + Returns: the dir of current script + """ + return os.path.basename(sys.argv[0]) + + +def get_script_self_path(): + """ + Returns: the dir of current script + """ + return os.path.realpath(sys.argv[0]) + + +def get_parent_dir(location, abspath=False): + """ + Args: + location: current directory or file + + Returns: + parent directory absolute or relative path + """ + _path = os.path.abspath if abspath else os.path.relpath + return _path(f_join(location, os.pardir)) + + +def md5_checksum(*fpath): + """ + File md5 signature + """ + hash_md5 = hashlib.md5() + with open(f_join(*fpath), "rb") as f: + for chunk in iter(lambda: f.read(65536), b""): + hash_md5.update(chunk) + return hash_md5.hexdigest() + + +def create_tar(fsrc, output_tarball, include=None, ignore=None, compress_mode="gz"): + """ + Args: + fsrc: source file or folder + output_tarball: output tar file name + compress_mode: "gz", "bz2", "xz" or "" (empty for uncompressed write) + include: include pattern, will trigger copy to temp directory + ignore: ignore pattern, will trigger copy to temp directory + """ + fsrc, output_tarball = f_expand(fsrc), f_expand(output_tarball) + assert compress_mode in ["gz", "bz2", "xz", ""] + src_base = os.path.basename(fsrc) + + tempdir = None + if include or ignore: + tempdir = tempfile.mkdtemp() + tempdest = f_join(tempdir, src_base) + f_copy(fsrc, tempdest, include=include, ignore=ignore) + fsrc = tempdest + + with tarfile.open(output_tarball, "w:" + compress_mode) as tar: + tar.add(fsrc, arcname=src_base) + + if tempdir: + f_remove(tempdir) + + +def extract_tar(source_tarball, output_dir=".", members=None): + """ + Args: + source_tarball: extract members from archive + output_dir: default to current working dir + members: must be a subset of the list returned by getmembers() + """ + source_tarball, output_dir = f_expand(source_tarball), f_expand(output_dir) + with tarfile.open(source_tarball, "r:*") as tar: + tar.extractall(output_dir, members=members) + + +def move_with_backup(*fpath, suffix=".bak"): + """ + Ensures that a path is not occupied. If there is a file, rename it by + adding @suffix. Resursively backs up everything. + + Args: + fpath: file path to clear + suffix: Add to backed up files (default: {'.bak'}) + """ + fpath = str(f_join(*fpath)) + if os.path.exists(fpath): + move_with_backup(fpath + suffix) + shutil.move(fpath, fpath + suffix) + + +def insert_before_ext(name, insert): + """ + log.txt -> log.ep50.txt + """ + name, ext = os.path.splitext(name) + return name + insert + ext + + +def timestamp_file_name(fname): + timestr = datetime.now().strftime("_%H-%M-%S_%m-%d-%y") + return insert_before_ext(fname, timestr) + + +def get_file_lock(*fpath, timeout: int = 15, logging_level="critical"): + """ + NFS-safe filesystem-backed lock. `pip install flufl.lock` + https://flufllock.readthedocs.io/en/stable/apiref.html + + Args: + fpath: should be a path on NFS so that every process can see it + timeout: seconds + """ + from flufl.lock import Lock + + logging.getLogger("flufl.lock").setLevel(logging_level.upper()) + return Lock(f_join(*fpath), lifetime=timeout) + + +def load_pickle(*fpaths): + with open(f_join(*fpaths), "rb") as fp: + return pickle.load(fp) + + +def dump_pickle(data, *fpaths): + with open(f_join(*fpaths), "wb") as fp: + pickle.dump(data, fp) + + +def load_text(*fpaths, by_lines=False): + with open(f_join(*fpaths), "r") as fp: + if by_lines: + return fp.readlines() + else: + return fp.read() + + +def load_text_lines(*fpaths): + return load_text(*fpaths, by_lines=True) + + +def dump_text(s, *fpaths): + with open(f_join(*fpaths), "w") as fp: + fp.write(s) + + +def dump_text_lines(lines: list[str], *fpaths, add_newline=True): + with open(f_join(*fpaths), "w") as fp: + for line in lines: + print(line, file=fp, end="\n" if add_newline else "") + + +# aliases to be consistent with other load_* and dump_* +pickle_load = load_pickle +pickle_dump = dump_pickle +text_load = load_text +read_text = load_text +read_text_lines = load_text_lines +write_text = dump_text +write_text_lines = dump_text_lines +text_dump = dump_text diff --git a/docs/src/voyager/utils/json_utils.py b/docs/src/voyager/utils/json_utils.py new file mode 100644 index 00000000..a1968faf --- /dev/null +++ b/docs/src/voyager/utils/json_utils.py @@ -0,0 +1,227 @@ +import json +import re +from typing import Any, Dict, Union +from .file_utils import f_join + + +def json_load(*file_path, **kwargs): + file_path = f_join(file_path) + with open(file_path, "r") as fp: + return json.load(fp, **kwargs) + + +def json_loads(string, **kwargs): + return json.loads(string, **kwargs) + + +def json_dump(data, *file_path, **kwargs): + file_path = f_join(file_path) + with open(file_path, "w") as fp: + json.dump(data, fp, **kwargs) + + +def json_dumps(data, **kwargs): + """ + Returns: string + """ + return json.dumps(data, **kwargs) + + +# ---------------- Aliases ----------------- +# add aliases where verb goes first, json_load -> load_json +load_json = json_load +loads_json = json_loads +dump_json = json_dump +dumps_json = json_dumps + + +def extract_char_position(error_message: str) -> int: + """Extract the character position from the JSONDecodeError message. + Args: + error_message (str): The error message from the JSONDecodeError + exception. + Returns: + int: The character position. + """ + import re + + char_pattern = re.compile(r"\(char (\d+)\)") + if match := char_pattern.search(error_message): + return int(match[1]) + else: + raise ValueError("Character position not found in the error message.") + + +def add_quotes_to_property_names(json_string: str) -> str: + """ + Add quotes to property names in a JSON string. + Args: + json_string (str): The JSON string. + Returns: + str: The JSON string with quotes added to property names. + """ + + def replace_func(match): + return f'"{match.group(1)}":' + + property_name_pattern = re.compile(r"(\w+):") + corrected_json_string = property_name_pattern.sub(replace_func, json_string) + + try: + json.loads(corrected_json_string) + return corrected_json_string + except json.JSONDecodeError as e: + raise e + + +def balance_braces(json_string: str) -> str: + """ + Balance the braces in a JSON string. + Args: + json_string (str): The JSON string. + Returns: + str: The JSON string with braces balanced. + """ + + open_braces_count = json_string.count("{") + close_braces_count = json_string.count("}") + + while open_braces_count > close_braces_count: + json_string += "}" + close_braces_count += 1 + + while close_braces_count > open_braces_count: + json_string = json_string.rstrip("}") + close_braces_count -= 1 + + try: + json.loads(json_string) + return json_string + except json.JSONDecodeError as e: + raise e + + +def fix_invalid_escape(json_str: str, error_message: str) -> str: + while error_message.startswith("Invalid \\escape"): + bad_escape_location = extract_char_position(error_message) + json_str = json_str[:bad_escape_location] + json_str[bad_escape_location + 1 :] + try: + json.loads(json_str) + return json_str + except json.JSONDecodeError as e: + error_message = str(e) + return json_str + + +def correct_json(json_str: str) -> str: + """ + Correct common JSON errors. + Args: + json_str (str): The JSON string. + """ + + try: + json.loads(json_str) + return json_str + except json.JSONDecodeError as e: + error_message = str(e) + if error_message.startswith("Invalid \\escape"): + json_str = fix_invalid_escape(json_str, error_message) + if error_message.startswith( + "Expecting property name enclosed in double quotes" + ): + json_str = add_quotes_to_property_names(json_str) + try: + json.loads(json_str) + return json_str + except json.JSONDecodeError as e: + error_message = str(e) + if balanced_str := balance_braces(json_str): + return balanced_str + return json_str + + +def fix_and_parse_json( + json_str: str, try_to_fix_with_gpt: bool = True +) -> Union[str, Dict[Any, Any]]: + """Fix and parse JSON string""" + try: + json_str = json_str.replace("\t", "") + return json.loads(json_str) + except json.JSONDecodeError as _: # noqa: F841 + json_str = correct_json(json_str) + try: + return json.loads(json_str) + except json.JSONDecodeError as _: # noqa: F841 + pass + # Let's do something manually: + # sometimes GPT responds with something BEFORE the braces: + # "I'm sorry, I don't understand. Please try again." + # {"text": "I'm sorry, I don't understand. Please try again.", + # "confidence": 0.0} + # So let's try to find the first brace and then parse the rest + # of the string + try: + brace_index = json_str.index("{") + json_str = json_str[brace_index:] + last_brace_index = json_str.rindex("}") + json_str = json_str[: last_brace_index + 1] + return json.loads(json_str) + except json.JSONDecodeError as e: # noqa: F841 + # if try_to_fix_with_gpt: + # print( + # "Warning: Failed to parse AI output, attempting to fix." + # "\n If you see this warning frequently, it's likely that" + # " your prompt is confusing the AI. Try changing it up" + # " slightly." + # ) + # # Now try to fix this up using the ai_functions + # ai_fixed_json = fix_json(json_str, JSON_SCHEMA) + # + # if ai_fixed_json != "failed": + # return json.loads(ai_fixed_json) + # else: + # # This allows the AI to react to the error message, + # # which usually results in it correcting its ways. + # print("Failed to fix ai output, telling the AI.") + # return json_str + # else: + raise e + + +# def fix_json(json_str: str, schema: str) -> str: +# """Fix the given JSON string to make it parseable and fully complient with the provided schema.""" +# +# # Try to fix the JSON using gpt: +# function_string = "def fix_json(json_str: str, schema:str=None) -> str:" +# args = [f"'''{json_str}'''", f"'''{schema}'''"] +# description_string = ( +# "Fixes the provided JSON string to make it parseable" +# " and fully complient with the provided schema.\n If an object or" +# " field specified in the schema isn't contained within the correct" +# " JSON, it is ommited.\n This function is brilliant at guessing" +# " when the format is incorrect." +# ) +# +# # If it doesn't already start with a "`", add one: +# if not json_str.startswith("`"): +# json_str = "```json\n" + json_str + "\n```" +# result_string = call_ai_function( +# function_string, args, description_string, model=cfg.fast_llm_model +# ) +# if cfg.debug: +# print("------------ JSON FIX ATTEMPT ---------------") +# print(f"Original JSON: {json_str}") +# print("-----------") +# print(f"Fixed JSON: {result_string}") +# print("----------- END OF FIX ATTEMPT ----------------") +# +# try: +# json.loads(result_string) # just check the validity +# return result_string +# except: # noqa: E722 +# # Get the call stack: +# # import traceback +# # call_stack = traceback.format_exc() +# # print(f"Failed to fix JSON: '{json_str}' "+call_stack) +# return "failed" diff --git a/docs/src/voyager/utils/record_utils.py b/docs/src/voyager/utils/record_utils.py new file mode 100644 index 00000000..6c168a4c --- /dev/null +++ b/docs/src/voyager/utils/record_utils.py @@ -0,0 +1,102 @@ +import time + +from .file_utils import * +from .json_utils import * + + +class EventRecorder: + def __init__( + self, + ckpt_dir="ckpt", + resume=False, + init_position=None, + ): + self.ckpt_dir = ckpt_dir + self.item_history = set() + self.item_vs_time = {} + self.item_vs_iter = {} + self.biome_history = set() + self.init_position = init_position + self.position_history = [[0, 0]] + self.elapsed_time = 0 + self.iteration = 0 + f_mkdir(self.ckpt_dir, "events") + if resume: + self.resume() + + def record(self, events, task): + task = re.sub(r'[\\/:"*?<>| ]', "_", task) + task = task.replace(" ", "_") + time.strftime( + "_%Y%m%d_%H%M%S", time.localtime() + ) + self.iteration += 1 + if not self.init_position: + self.init_position = [ + events[0][1]["status"]["position"]["x"], + events[0][1]["status"]["position"]["z"], + ] + for event_type, event in events: + self.update_items(event) + if event_type == "observe": + self.update_elapsed_time(event) + print( + f"\033[96m****Recorder message: {self.elapsed_time} ticks have elapsed****\033[0m\n" + f"\033[96m****Recorder message: {self.iteration} iteration passed****\033[0m" + ) + dump_json(events, f_join(self.ckpt_dir, "events", task)) + + def resume(self, cutoff=None): + self.item_history = set() + self.item_vs_time = {} + self.item_vs_iter = {} + self.elapsed_time = 0 + self.position_history = [[0, 0]] + + def get_timestamp(string): + timestamp = "_".join(string.split("_")[-2:]) + return time.mktime(time.strptime(timestamp, "%Y%m%d_%H%M%S")) + + records = f_listdir(self.ckpt_dir, "events") + sorted_records = sorted(records, key=get_timestamp) + for record in sorted_records: + self.iteration += 1 + if cutoff and self.iteration > cutoff: + break + events = load_json(f_join(self.ckpt_dir, "events", record)) + if not self.init_position: + self.init_position = ( + events[0][1]["status"]["position"]["x"], + events[0][1]["status"]["position"]["z"], + ) + for event_type, event in events: + self.update_items(event) + self.update_position(event) + if event_type == "observe": + self.update_elapsed_time(event) + + def update_items(self, event): + inventory = event["inventory"] + elapsed_time = event["status"]["elapsedTime"] + biome = event["status"]["biome"] + items = set(inventory.keys()) + new_items = items - self.item_history + self.item_history.update(items) + self.biome_history.add(biome) + if new_items: + if self.elapsed_time + elapsed_time not in self.item_vs_time: + self.item_vs_time[self.elapsed_time + elapsed_time] = [] + self.item_vs_time[self.elapsed_time + elapsed_time].extend(new_items) + if self.iteration not in self.item_vs_iter: + self.item_vs_iter[self.iteration] = [] + self.item_vs_iter[self.iteration].extend(new_items) + + def update_elapsed_time(self, event): + self.elapsed_time += event["status"]["elapsedTime"] + + def update_position(self, event): + position = [ + event["status"]["position"]["x"] - self.init_position[0], + event["status"]["position"]["z"] - self.init_position[1], + ] + if self.position_history[-1] != position: + self.position_history.append(position) diff --git a/docs/src/voyager/voyager.py b/docs/src/voyager/voyager.py new file mode 100644 index 00000000..61a0c207 --- /dev/null +++ b/docs/src/voyager/voyager.py @@ -0,0 +1,411 @@ +import copy +import json +import os +import time +from typing import Dict + +import voyager.utils as U +from .env import VoyagerEnv + +from .agents import ActionAgent +from .agents import CriticAgent +from .agents import CurriculumAgent +from .agents import SkillManager + + +# TODO: remove event memory +class Voyager: + def __init__( + self, + mc_port: int = None, + azure_login: Dict[str, str] = None, + server_port: int = 3000, + openai_api_key: str = None, + env_wait_ticks: int = 20, + env_request_timeout: int = 600, + max_iterations: int = 160, + reset_placed_if_failed: bool = False, + action_agent_model_name: str = "gpt-4", + action_agent_temperature: float = 0, + action_agent_task_max_retries: int = 4, + action_agent_show_chat_log: bool = True, + action_agent_show_execution_error: bool = True, + curriculum_agent_model_name: str = "gpt-4", + curriculum_agent_temperature: float = 0, + curriculum_agent_qa_model_name: str = "gpt-3.5-turbo", + curriculum_agent_qa_temperature: float = 0, + curriculum_agent_warm_up: Dict[str, int] = None, + curriculum_agent_core_inventory_items: str = r".*_log|.*_planks|stick|crafting_table|furnace" + r"|cobblestone|dirt|coal|.*_pickaxe|.*_sword|.*_axe", + curriculum_agent_mode: str = "auto", + critic_agent_model_name: str = "gpt-4", + critic_agent_temperature: float = 0, + critic_agent_mode: str = "auto", + skill_manager_model_name: str = "gpt-3.5-turbo", + skill_manager_temperature: float = 0, + skill_manager_retrieval_top_k: int = 5, + openai_api_request_timeout: int = 240, + ckpt_dir: str = "ckpt", + skill_library_dir: str = None, + resume: bool = False, + ): + """ + The main class for Voyager. + Action agent is the iterative prompting mechanism in paper. + Curriculum agent is the automatic curriculum in paper. + Critic agent is the self-verification in paper. + Skill manager is the skill library in paper. + :param mc_port: minecraft in-game port + :param azure_login: minecraft login config + :param server_port: mineflayer port + :param openai_api_key: openai api key + :param env_wait_ticks: how many ticks at the end each step will wait, if you found some chat log missing, + you should increase this value + :param env_request_timeout: how many seconds to wait for each step, if the code execution exceeds this time, + python side will terminate the connection and need to be resumed + :param reset_placed_if_failed: whether to reset placed blocks if failed, useful for building task + :param action_agent_model_name: action agent model name + :param action_agent_temperature: action agent temperature + :param action_agent_task_max_retries: how many times to retry if failed + :param curriculum_agent_model_name: curriculum agent model name + :param curriculum_agent_temperature: curriculum agent temperature + :param curriculum_agent_qa_model_name: curriculum agent qa model name + :param curriculum_agent_qa_temperature: curriculum agent qa temperature + :param curriculum_agent_warm_up: info will show in curriculum human message + if completed task larger than the value in dict, available keys are: + { + "context": int, + "biome": int, + "time": int, + "other_blocks": int, + "nearby_entities": int, + "health": int, + "hunger": int, + "position": int, + "equipment": int, + "chests": int, + "optional_inventory_items": int, + } + :param curriculum_agent_core_inventory_items: only show these items in inventory before optional_inventory_items + reached in warm up + :param curriculum_agent_mode: "auto" for automatic curriculum, "manual" for human curriculum + :param critic_agent_model_name: critic agent model name + :param critic_agent_temperature: critic agent temperature + :param critic_agent_mode: "auto" for automatic critic ,"manual" for human critic + :param skill_manager_model_name: skill manager model name + :param skill_manager_temperature: skill manager temperature + :param skill_manager_retrieval_top_k: how many skills to retrieve for each task + :param openai_api_request_timeout: how many seconds to wait for openai api + :param ckpt_dir: checkpoint dir + :param skill_library_dir: skill library dir + :param resume: whether to resume from checkpoint + """ + # init env + self.env = VoyagerEnv( + mc_port=mc_port, + azure_login=azure_login, + server_port=server_port, + request_timeout=env_request_timeout, + ) + self.env_wait_ticks = env_wait_ticks + self.reset_placed_if_failed = reset_placed_if_failed + self.max_iterations = max_iterations + + # set openai api key + os.environ["OPENAI_API_KEY"] = openai_api_key + + # init agents + self.action_agent = ActionAgent( + model_name=action_agent_model_name, + temperature=action_agent_temperature, + request_timout=openai_api_request_timeout, + ckpt_dir=ckpt_dir, + resume=resume, + chat_log=action_agent_show_chat_log, + execution_error=action_agent_show_execution_error, + ) + self.action_agent_task_max_retries = action_agent_task_max_retries + self.curriculum_agent = CurriculumAgent( + model_name=curriculum_agent_model_name, + temperature=curriculum_agent_temperature, + qa_model_name=curriculum_agent_qa_model_name, + qa_temperature=curriculum_agent_qa_temperature, + request_timout=openai_api_request_timeout, + ckpt_dir=ckpt_dir, + resume=resume, + mode=curriculum_agent_mode, + warm_up=curriculum_agent_warm_up, + core_inventory_items=curriculum_agent_core_inventory_items, + ) + self.critic_agent = CriticAgent( + model_name=critic_agent_model_name, + temperature=critic_agent_temperature, + request_timout=openai_api_request_timeout, + mode=critic_agent_mode, + ) + self.skill_manager = SkillManager( + model_name=skill_manager_model_name, + temperature=skill_manager_temperature, + retrieval_top_k=skill_manager_retrieval_top_k, + request_timout=openai_api_request_timeout, + ckpt_dir=skill_library_dir if skill_library_dir else ckpt_dir, + resume=True if resume or skill_library_dir else False, + ) + self.recorder = U.EventRecorder(ckpt_dir=ckpt_dir, resume=resume) + self.resume = resume + + # init variables for rollout + self.action_agent_rollout_num_iter = -1 + self.task = None + self.context = "" + self.messages = None + self.conversations = [] + self.last_events = None + + def reset(self, task, context="", reset_env=True): + self.action_agent_rollout_num_iter = 0 + self.task = task + self.context = context + if reset_env: + self.env.reset( + options={ + "mode": "soft", + "wait_ticks": self.env_wait_ticks, + } + ) + difficulty = ( + "easy" if len(self.curriculum_agent.completed_tasks) > 15 else "peaceful" + ) + # step to peek an observation + events = self.env.step( + "bot.chat(`/time set ${getNextTime()}`);\n" + + f"bot.chat('/difficulty {difficulty}');" + ) + skills = self.skill_manager.retrieve_skills(query=self.context) + print( + f"\033[33mRender Action Agent system message with {len(skills)} skills\033[0m" + ) + system_message = self.action_agent.render_system_message(skills=skills) + human_message = self.action_agent.render_human_message( + events=events, code="", task=self.task, context=context, critique="" + ) + self.messages = [system_message, human_message] + print( + f"\033[32m****Action Agent human message****\n{human_message.content}\033[0m" + ) + assert len(self.messages) == 2 + self.conversations = [] + return self.messages + + def close(self): + self.env.close() + + def step(self): + if self.action_agent_rollout_num_iter < 0: + raise ValueError("Agent must be reset before stepping") + ai_message = self.action_agent.llm(self.messages) + print(f"\033[34m****Action Agent ai message****\n{ai_message.content}\033[0m") + self.conversations.append( + (self.messages[0].content, self.messages[1].content, ai_message.content) + ) + parsed_result = self.action_agent.process_ai_message(message=ai_message) + success = False + if isinstance(parsed_result, dict): + code = parsed_result["program_code"] + "\n" + parsed_result["exec_code"] + events = self.env.step( + code, + programs=self.skill_manager.programs, + ) + self.recorder.record(events, self.task) + self.action_agent.update_chest_memory(events[-1][1]["nearbyChests"]) + success, critique = self.critic_agent.check_task_success( + events=events, + task=self.task, + context=self.context, + chest_observation=self.action_agent.render_chest_observation(), + max_retries=5, + ) + + if self.reset_placed_if_failed and not success: + # revert all the placing event in the last step + blocks = [] + positions = [] + for event_type, event in events: + if event_type == "onSave" and event["onSave"].endswith("_placed"): + block = event["onSave"].split("_placed")[0] + position = event["status"]["position"] + blocks.append(block) + positions.append(position) + new_events = self.env.step( + f"await givePlacedItemBack(bot, {U.json_dumps(blocks)}, {U.json_dumps(positions)})", + programs=self.skill_manager.programs, + ) + events[-1][1]["inventory"] = new_events[-1][1]["inventory"] + events[-1][1]["voxels"] = new_events[-1][1]["voxels"] + new_skills = self.skill_manager.retrieve_skills( + query=self.context + + "\n\n" + + self.action_agent.summarize_chatlog(events) + ) + system_message = self.action_agent.render_system_message(skills=new_skills) + human_message = self.action_agent.render_human_message( + events=events, + code=parsed_result["program_code"], + task=self.task, + context=self.context, + critique=critique, + ) + self.last_events = copy.deepcopy(events) + self.messages = [system_message, human_message] + else: + assert isinstance(parsed_result, str) + self.recorder.record([], self.task) + print(f"\033[34m{parsed_result} Trying again!\033[0m") + assert len(self.messages) == 2 + self.action_agent_rollout_num_iter += 1 + done = ( + self.action_agent_rollout_num_iter >= self.action_agent_task_max_retries + or success + ) + info = { + "task": self.task, + "success": success, + "conversations": self.conversations, + } + if success: + assert ( + "program_code" in parsed_result and "program_name" in parsed_result + ), "program and program_name must be returned when success" + info["program_code"] = parsed_result["program_code"] + info["program_name"] = parsed_result["program_name"] + else: + print( + f"\033[32m****Action Agent human message****\n{self.messages[-1].content}\033[0m" + ) + return self.messages, 0, done, info + + def rollout(self, *, task, context, reset_env=True): + self.reset(task=task, context=context, reset_env=reset_env) + while True: + messages, reward, done, info = self.step() + if done: + break + return messages, reward, done, info + + def learn(self, reset_env=True): + if self.resume: + # keep the inventory + self.env.reset( + options={ + "mode": "soft", + "wait_ticks": self.env_wait_ticks, + } + ) + else: + # clear the inventory + self.env.reset( + options={ + "mode": "hard", + "wait_ticks": self.env_wait_ticks, + } + ) + self.resume = True + self.last_events = self.env.step("") + + while True: + if self.recorder.iteration > self.max_iterations: + print("Iteration limit reached") + break + task, context = self.curriculum_agent.propose_next_task( + events=self.last_events, + chest_observation=self.action_agent.render_chest_observation(), + max_retries=5, + ) + print( + f"\033[35mStarting task {task} for at most {self.action_agent_task_max_retries} times\033[0m" + ) + try: + messages, reward, done, info = self.rollout( + task=task, + context=context, + reset_env=reset_env, + ) + except Exception as e: + time.sleep(3) # wait for mineflayer to exit + info = { + "task": task, + "success": False, + } + # reset bot status here + self.last_events = self.env.reset( + options={ + "mode": "hard", + "wait_ticks": self.env_wait_ticks, + "inventory": self.last_events[-1][1]["inventory"], + "equipment": self.last_events[-1][1]["status"]["equipment"], + "position": self.last_events[-1][1]["status"]["position"], + } + ) + # use red color background to print the error + print("Your last round rollout terminated due to error:") + print(f"\033[41m{e}\033[0m") + + if info["success"]: + self.skill_manager.add_new_skill(info) + + self.curriculum_agent.update_exploration_progress(info) + print( + f"\033[35mCompleted tasks: {', '.join(self.curriculum_agent.completed_tasks)}\033[0m" + ) + print( + f"\033[35mFailed tasks: {', '.join(self.curriculum_agent.failed_tasks)}\033[0m" + ) + + return { + "completed_tasks": self.curriculum_agent.completed_tasks, + "failed_tasks": self.curriculum_agent.failed_tasks, + "skills": self.skill_manager.skills, + } + + def decompose_task(self, task): + if not self.last_events: + self.last_events = self.env.reset( + options={ + "mode": "hard", + "wait_ticks": self.env_wait_ticks, + } + ) + return self.curriculum_agent.decompose_task(task, self.last_events) + + def inference(self, task=None, sub_goals=[], reset_mode="hard", reset_env=True): + if not task and not sub_goals: + raise ValueError("Either task or sub_goals must be provided") + if not sub_goals: + sub_goals = self.decompose_task(task) + self.env.reset( + options={ + "mode": reset_mode, + "wait_ticks": self.env_wait_ticks, + } + ) + self.curriculum_agent.completed_tasks = [] + self.curriculum_agent.failed_tasks = [] + self.last_events = self.env.step("") + while self.curriculum_agent.progress < len(sub_goals): + next_task = sub_goals[self.curriculum_agent.progress] + context = self.curriculum_agent.get_task_context(next_task) + print( + f"\033[35mStarting task {next_task} for at most {self.action_agent_task_max_retries} times\033[0m" + ) + messages, reward, done, info = self.rollout( + task=next_task, + context=context, + reset_env=reset_env, + ) + self.curriculum_agent.update_exploration_progress(info) + print( + f"\033[35mCompleted tasks: {', '.join(self.curriculum_agent.completed_tasks)}\033[0m" + ) + print( + f"\033[35mFailed tasks: {', '.join(self.curriculum_agent.failed_tasks)}\033[0m" + ) diff --git a/docs/tree.html b/docs/tree.html new file mode 100644 index 00000000..6044ec6d --- /dev/null +++ b/docs/tree.html @@ -0,0 +1,203 @@ + + + + + + + + + Project structure of: MineDojo/Voyager + + + + + + +
+

Project structure of: MineDojo/Voyager

+
    +
  • Voyager Root directory: Organizes subdirectories and files.
      +
    • FAQ.md Voyager FAQ: Solutions, Limitations, Costs.
    • +
    • installation Minecraft Fabric mods and instance installation guide +
    • +
    • README.md Intelligent Minecraft agent, MIT licensed, outperforms SOTA.
    • +
    • requirements.txt Project dependencies: tqdm, langchain, etc.
    • +
    • setup.py Python package setup script
    • +
    • skill_library
        +
      • README.md Skill Library: Contribution and Sharing Guide
      • +
      +
    • +
    • voyager Explore, evaluate, manage Voyager AI communication utilities.
        +
      • __init__.py Imports Voyager class from voyager module.
      • +
      • agents Skill management, evaluation, and interaction for Voyager Agents.
          +
        • __init__.py Imports Voyager's agents and SkillManager.
        • +
        • action.py ActionAgent handles agent behavior with ChatOpenAI and error handling.
        • +
        • critic.py CriticAgent: Evaluates, generates messages for Voyager project.
        • +
        • curriculum.py Curriculum agent manages chat-based learning, QA interactions.
        • +
        • skill.py SkillManager initializes, manages skills with VectorDB.
        • +
        +
      • +
      • control_primitives Control primitives for game objects and bots.
          +
        • __init__.py Imports control primitives and loads them as text files.
        • +
        • craftHelper.js Finds minimum recipe for items, informs users of missing crafting materials.
        • +
        • craftItem.js Craft items using crafting table, validate inputs, 10 tries before error.
        • +
        • exploreUntil.js Explores tasks with a time limit, resolves result or null.
        • +
        • givePlacedItemBack.js Enables, disables doTileDrops and updates history.
        • +
        • killMob.js Kills nearest entity, auto-attacks, saves progress
        • +
        • mineBlock.js Mines blocks using bot's collect method.
        • +
        • placeItem.js Place item: Verify, position, equipment, save or report error.
        • +
        • shoot.js Shoot: Bot shoots valid weapon at nearby target in inventory.
        • +
        • smeltItem.js Smelt items using furnace with error handling.
        • +
        • useChest.js Enables bot-controlled chest interaction.
        • +
        • waitForMobRemoved.js Waits for entity removal, stops attacking.
        • +
        +
      • +
      • control_primitives_context Context-aware navigation and actions primitives directory
          +
        • __init__.py Loads control primitives from "voyager" package.
        • +
        • craftItem.js Crafts items by recipe, needs crafting table.
        • +
        • exploreUntil.js Explore iron and pigs using exploreUntil.
        • +
        • killMob.js Kill mob, collect item.
        • +
        • mineBlock.js Mines specified block within range, ignores errors.
        • +
        • mineflayer.js Control Mineflayer bot's actions and equipment for effective gameplay.
        • +
        • placeItem.js Place item near player using pathfinder
        • +
        • smeltItem.js Smelts items using fuel in a furnace, waits 12 seconds per smelt.
        • +
        • useChest.js Interacts with chests for item deposition.
        • +
        +
      • +
      • env Manage Minecraft instance environment and processes. +
      • +
      • prompts Minecraft biome-driven prompts and evaluation. +
      • +
      • utils Voyager utilities and file system ops, JSON events.
          +
        • __init__.py Utility functions and classes for Voyager module.
        • +
        • file_utils.py Utility functions for file system operations and management.
        • +
        • json_utils.py JSON handling utilities with error correction and validation.
        • +
        • record_utils.py EventRecorder: Records, updates, and sorts event timestamps.
        • +
        +
      • +
      • voyager.py Voyager: AI communication, environment setup, rollout control.
      • +
      +
    • +
    +
  • +
+
+ + + + \ No newline at end of file diff --git a/skill_library/.fdignore b/skill_library/.fdignore new file mode 100644 index 00000000..07cb9ac0 --- /dev/null +++ b/skill_library/.fdignore @@ -0,0 +1 @@ +trial* \ No newline at end of file diff --git a/voyager/env/.fdignore b/voyager/env/.fdignore new file mode 100644 index 00000000..239db9e0 --- /dev/null +++ b/voyager/env/.fdignore @@ -0,0 +1 @@ +mineflayer \ No newline at end of file