plancraft 0.1.0__py3-none-any.whl → 0.1.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,74 @@
1
+ Metadata-Version: 2.1
2
+ Name: plancraft
3
+ Version: 0.1.2
4
+ Summary: Plancraft: an evaluation dataset for planning with LLM agents
5
+ Requires-Python: >=3.9
6
+ Description-Content-Type: text/markdown
7
+ License-File: LICENSE
8
+ Requires-Dist: accelerate>=1.0.1
9
+ Requires-Dist: coloredlogs>=10.0
10
+ Requires-Dist: daemoniker>=0.2.3
11
+ Requires-Dist: datasets>=3.0.2
12
+ Requires-Dist: dill>=0.3.1.1
13
+ Requires-Dist: einops>=0.8.0
14
+ Requires-Dist: flaky>=3.8.1
15
+ Requires-Dist: hf-transfer>=0.1.8
16
+ Requires-Dist: huggingface-hub>=0.26.1
17
+ Requires-Dist: hydra-core>=1.3.2
18
+ Requires-Dist: imagehash>=4.0.0
19
+ Requires-Dist: imageio>=2.36.0
20
+ Requires-Dist: inflection>=0.3.1
21
+ Requires-Dist: ipython>=7.5.0
22
+ Requires-Dist: jinja2>=2.11.2
23
+ Requires-Dist: loguru>=0.7.2
24
+ Requires-Dist: lxml>=4.3.3
25
+ Requires-Dist: matplotlib>=3.9.2
26
+ Requires-Dist: networkx>=3.2.1
27
+ Requires-Dist: numpy<1.24,>=1.16.2
28
+ Requires-Dist: openai>=1.52.2
29
+ Requires-Dist: opencv-python>=4.1.0.25
30
+ Requires-Dist: pandas>=2.1.0
31
+ Requires-Dist: peft>=0.13.2
32
+ Requires-Dist: pillow>=8.0.0
33
+ Requires-Dist: psutil>=5.6.2
34
+ Requires-Dist: pydantic>=2.9.2
35
+ Requires-Dist: pyglet>=2.0.18
36
+ Requires-Dist: pyro4>=4.76
37
+ Requires-Dist: python-dotenv>=1.0.1
38
+ Requires-Dist: pyyaml>=6.0.2
39
+ Requires-Dist: requests>=2.20.0
40
+ Requires-Dist: seaborn>=0.13.2
41
+ Requires-Dist: setuptools>=49.2.0
42
+ Requires-Dist: tinydb>=4.8.2
43
+ Requires-Dist: torch>=2.5.0
44
+ Requires-Dist: torchvision>=0.20.0
45
+ Requires-Dist: tqdm>=4.32.2
46
+ Requires-Dist: transformers>=4.43.3
47
+ Requires-Dist: typing>=3.6.6
48
+ Requires-Dist: wandb>=0.18.5
49
+ Requires-Dist: xmltodict==0.12.0
50
+ Provides-Extra: full
51
+ Requires-Dist: gym<=0.23.1,>=0.19.0; extra == "full"
52
+
53
+ # plancraft
54
+
55
+ [![Test](https://github.com/gautierdag/plancraft/actions/workflows/test.yaml/badge.svg)](https://github.com/gautierdag/plancraft/actions/workflows/test.yaml)
56
+ ![Python Version](https://img.shields.io/badge/python-3.9+-blue)
57
+ ![Ruff](https://img.shields.io/badge/linter-ruff-blue)
58
+ [![PyPI Version](https://img.shields.io/pypi/v/plancraft)](https://pypi.org/project/plancraft/)
59
+
60
+ Plancraft is a minecraft environment and agent that innovates on planning LLM agents with a retriever
61
+
62
+ You can install the package by running the following command:
63
+
64
+ ```bash
65
+ pip install plancraft
66
+ ```
67
+
68
+ Should you need the multimodal version of the package, you will also need a custom [fork](https://github.com/gautierdag/minerl.git) of the minerl package. You can install it by running the following command:
69
+
70
+ ```bash
71
+ pip install git+hhttps://github.com/gautierdag/minerl.git
72
+ ```
73
+
74
+ Note that you may need to follow the same installation instructions as in the [minerl documentation](https://minerl.readthedocs.io/en/latest/tutorials/index.html).
@@ -0,0 +1,5 @@
1
+ plancraft-0.1.2.dist-info/LICENSE,sha256=YGR8ehDB4t-T-lOQKMfKNR-2zsOU7E3E5NA8t25HKE0,1070
2
+ plancraft-0.1.2.dist-info/METADATA,sha256=IDsYHFCvjx4Nhe4Jgyj0x7YsIVAR95ZyrSA-LIQBgeI,2631
3
+ plancraft-0.1.2.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
4
+ plancraft-0.1.2.dist-info/top_level.txt,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
5
+ plancraft-0.1.2.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.5.0)
2
+ Generator: setuptools (75.6.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -0,0 +1 @@
1
+
environments/__init__.py DELETED
File without changes
environments/actions.py DELETED
@@ -1,218 +0,0 @@
1
- from typing import Union
2
-
3
- from pydantic import BaseModel, field_validator, model_validator
4
-
5
-
6
- def convert_to_slot_index(slot: str) -> int:
7
- slot = slot.strip()
8
- grid_map = {
9
- "[0]": 0,
10
- "[A1]": 1,
11
- "[A2]": 2,
12
- "[A3]": 3,
13
- "[B1]": 4,
14
- "[B2]": 5,
15
- "[B3]": 6,
16
- "[C1]": 7,
17
- "[C2]": 8,
18
- "[C3]": 9,
19
- }
20
- if slot in grid_map:
21
- return grid_map[slot]
22
- else:
23
- return int(slot[2:-1]) + 9
24
-
25
-
26
- def convert_from_slot_index(slot_index: int) -> str:
27
- grid_map = {
28
- 0: "[0]",
29
- 1: "[A1]",
30
- 2: "[A2]",
31
- 3: "[A3]",
32
- 4: "[B1]",
33
- 5: "[B2]",
34
- 6: "[B3]",
35
- 7: "[C1]",
36
- 8: "[C2]",
37
- 9: "[C3]",
38
- }
39
- if slot_index < 10:
40
- return grid_map[slot_index]
41
- else:
42
- return f"[I{slot_index-9}]"
43
-
44
-
45
- class SymbolicMoveAction(BaseModel):
46
- """ "Moves an item from one slot to another"""
47
-
48
- slot_from: int
49
- slot_to: int
50
- quantity: int
51
- action_type: str = "move"
52
-
53
- @field_validator("action_type", mode="before")
54
- def fix_action_type(cls, value) -> str:
55
- return "move"
56
-
57
- @field_validator("slot_from", "slot_to", mode="before")
58
- def transform_str_to_int(cls, value) -> int:
59
- # if value is a string like [A1] or [I1], convert it to an integer
60
- if isinstance(value, str):
61
- try:
62
- return convert_to_slot_index(value)
63
- except ValueError:
64
- raise AttributeError(
65
- "slot_from and slot_to must be [0] or [A1] to [C3] or [I1] to [I36]"
66
- )
67
- return value
68
-
69
- @field_validator("quantity", mode="before")
70
- def transform_quantity(cls, value) -> int:
71
- if isinstance(value, str):
72
- try:
73
- return int(value)
74
- except ValueError:
75
- raise AttributeError("quantity must be an integer")
76
- return value
77
-
78
- @model_validator(mode="after")
79
- def validate(self):
80
- if self.slot_from == self.slot_to:
81
- raise AttributeError("slot_from and slot_to must be different")
82
- if self.slot_from < 0 or self.slot_from > 45:
83
- raise AttributeError("slot_from must be between 0 and 45")
84
- if self.slot_to < 1 or self.slot_to > 45:
85
- raise AttributeError("slot_to must be between 1 and 45")
86
- if self.quantity < 1 or self.quantity > 64:
87
- raise AttributeError("quantity must be between 1 and 64")
88
-
89
- def to_action_dict(self) -> dict:
90
- return {
91
- "inventory_command": [self.slot_from, self.slot_to, self.quantity],
92
- }
93
-
94
-
95
- class SymbolicSmeltAction(BaseModel):
96
- """Smelts an item and moves the result into a new slot"""
97
-
98
- slot_from: int
99
- slot_to: int
100
- quantity: int
101
- action_type: str = "smelt"
102
-
103
- @field_validator("action_type", mode="before")
104
- def fix_action_type(cls, value) -> str:
105
- return "smelt"
106
-
107
- @field_validator("slot_from", "slot_to", mode="before")
108
- def transform_str_to_int(cls, value) -> int:
109
- # if value is a string like [A1] or [I1], convert it to an integer
110
- if isinstance(value, str):
111
- try:
112
- return convert_to_slot_index(value)
113
- except ValueError:
114
- raise AttributeError(
115
- "slot_from and slot_to must be [0] or [A1] to [C3] or [I1] to [I36]"
116
- )
117
- return value
118
-
119
- @field_validator("quantity", mode="before")
120
- def transform_quantity(cls, value) -> int:
121
- if isinstance(value, str):
122
- try:
123
- return int(value)
124
- except ValueError:
125
- raise AttributeError("quantity must be an integer")
126
- return value
127
-
128
- @model_validator(mode="after")
129
- def validate(self):
130
- if self.slot_from == self.slot_to:
131
- raise AttributeError("slot_from and slot_to must be different")
132
- if self.slot_from < 0 or self.slot_from > 45:
133
- raise AttributeError("slot_from must be between 0 and 45")
134
- if self.slot_to < 1 or self.slot_to > 45:
135
- raise AttributeError("slot_to must be between 1 and 45")
136
- if self.quantity < 1 or self.quantity > 64:
137
- raise AttributeError("quantity must be between 1 and 64")
138
-
139
- def to_action_dict(self) -> dict:
140
- return {
141
- "smelt": [self.slot_from, self.slot_to, self.quantity],
142
- }
143
-
144
-
145
- class ThinkAction(BaseModel):
146
- """Think about the answer before answering"""
147
-
148
- thought: str
149
-
150
- def to_action_dict(self) -> dict:
151
- return {}
152
-
153
-
154
- class SearchAction(BaseModel):
155
- """Searches for a relevant document in the wiki"""
156
-
157
- search_string: str
158
-
159
- def to_action_dict(self) -> dict:
160
- return {
161
- "search": self.search_string,
162
- }
163
-
164
-
165
- class RealActionInteraction(BaseModel):
166
- mouse_direction_x: float = 0
167
- mouse_direction_y: float = 0
168
- right_click: bool = False
169
- left_click: bool = False
170
-
171
- @field_validator("mouse_direction_x", "mouse_direction_y")
172
- def prevent_zero(cls, v):
173
- if v > 10:
174
- return 10
175
- elif v < -10:
176
- return -10
177
- return v
178
-
179
- def to_action_dict(self) -> dict:
180
- return {
181
- "camera": [self.mouse_direction_x, self.mouse_direction_y],
182
- "use": int(self.right_click),
183
- "attack": int(self.left_click),
184
- }
185
-
186
-
187
- class StopAction(BaseModel):
188
- """
189
- Action that model can take to stop planning - decide impossible to continue
190
- Note: also known as the "impossible" action
191
- """
192
-
193
- reason: str = ""
194
-
195
-
196
- class NoOp(SymbolicMoveAction):
197
- """No operation action - special instance of move"""
198
-
199
- def __init__(self):
200
- super().__init__(slot_from=0, slot_to=1, quantity=1)
201
- self.slot_to = 0
202
-
203
- def __call__(self, *args, **kwargs):
204
- return None
205
-
206
- def __str__(self):
207
- return "NoOp"
208
-
209
-
210
- # when symbolic action is true, can either move objects around or smelt
211
- SymbolicAction = SymbolicMoveAction # | SymbolicSmeltAction
212
-
213
- # when symbolic action is false, then need to use mouse to move things around, but can use smelt action
214
- RealAction = RealActionInteraction | SymbolicSmeltAction
215
-
216
-
217
- class PydanticSymbolicAction(BaseModel):
218
- root: Union[SymbolicMoveAction, SymbolicSmeltAction]
environments/env_real.py DELETED
@@ -1,315 +0,0 @@
1
- from typing import Sequence, Union
2
-
3
- import numpy as np
4
- import json
5
-
6
-
7
- try:
8
- from minerl.env import _singleagent
9
- from minerl.herobraine.env_specs.human_controls import HumanControlEnvSpec
10
- from minerl.herobraine.hero import handlers, mc, spaces
11
- from minerl.herobraine.hero.handler import Handler
12
- from minerl.herobraine.hero.handlers.agent.action import Action
13
- from minerl.herobraine.hero.handlers.agent.start import InventoryAgentStart
14
- from minerl.herobraine.hero.handlers.translation import TranslationHandler
15
- except ImportError as e:
16
- raise ImportError(
17
- "The 'minerl' package is required to use RealPlancraft. "
18
- "Please install it using 'pip install plancraft[full]' or 'pip install minerl'."
19
- ) from e
20
-
21
-
22
- from plancraft.environments.actions import RealAction
23
-
24
-
25
- class InventoryCommandAction(Action):
26
- """
27
- Handler which lets agents programmatically interact with an open container
28
-
29
- Using this - agents can move a chosen quantity of items from one slot to another.
30
- """
31
-
32
- def to_string(self):
33
- return "inventory_command"
34
-
35
- def xml_template(self) -> str:
36
- return str("<InventoryCommands/>")
37
-
38
- def __init__(self):
39
- self._command = "inventory_command"
40
- # first argument is the slot to take from
41
- # second is the slot to put into
42
- # third is the count to take
43
- super().__init__(
44
- self.command,
45
- spaces.Tuple(
46
- (
47
- spaces.Discrete(46),
48
- spaces.Discrete(46),
49
- spaces.Discrete(64),
50
- )
51
- ),
52
- )
53
-
54
- def from_universal(self, x):
55
- return np.array([0, 0, 0], dtype=np.int32)
56
-
57
-
58
- class SmeltCommandAction(Action):
59
- """
60
- An action handler for smelting an item
61
- We assume smelting is immediate.
62
- @TODO: might be interesting to explore using the smelting time as an additional planning parameter.
63
-
64
- Using this agents can smelt items in their inventory.
65
- """
66
-
67
- def __init__(self):
68
- self._command = "smelt"
69
- # first argument is the slot to take from
70
- # second is the slot to put into
71
- # third is the count to smelt
72
- super().__init__(
73
- self.command,
74
- spaces.Tuple(
75
- (
76
- spaces.Discrete(46),
77
- spaces.Discrete(46),
78
- spaces.Discrete(64),
79
- )
80
- ),
81
- )
82
-
83
- def to_string(self):
84
- return "smelt"
85
-
86
- def xml_template(self) -> str:
87
- return str("<SmeltCommands/>")
88
-
89
- def from_universal(self, x):
90
- return np.array([0, 0, 0], dtype=np.int32)
91
-
92
-
93
- class InventoryResetAction(Action):
94
- def __init__(self):
95
- self._command = "inventory_reset"
96
- super().__init__(self._command, spaces.Text([1]))
97
-
98
- def to_string(self) -> str:
99
- return "inventory_reset"
100
-
101
- def to_hero(self, inventory_items: list[dict]):
102
- return "{} {}".format(self._command, json.dumps(inventory_items))
103
-
104
- def xml_template(self) -> str:
105
- return "<InventoryResetCommands/>"
106
-
107
- def from_universal(self, x):
108
- return []
109
-
110
-
111
- MINUTE = 20 * 60
112
-
113
-
114
- class CustomInventoryAgentStart(InventoryAgentStart):
115
- def __init__(self, inventory: list[dict[str, Union[str, int]]]):
116
- super().__init__({item["slot"]: item for item in inventory})
117
-
118
-
119
- class CraftingTableOnly(Handler):
120
- def to_string(self):
121
- return "start_with_crafting_table"
122
-
123
- def xml_template(self) -> str:
124
- return "<CraftingTableOnly>true</CraftingTableOnly>"
125
-
126
-
127
- class InventoryObservation(TranslationHandler):
128
- """
129
- Handles GUI Workbench Observations for selected items
130
- """
131
-
132
- def to_string(self):
133
- return "inventory"
134
-
135
- def xml_template(self) -> str:
136
- return str("""<ObservationFromFullInventory flat="false"/>""")
137
-
138
- def __init__(self, item_list, _other="other"):
139
- item_list = sorted(item_list)
140
- super().__init__(
141
- spaces.Dict(
142
- spaces={
143
- k: spaces.Box(
144
- low=0,
145
- high=2304,
146
- shape=(),
147
- dtype=np.int32,
148
- normalizer_scale="log",
149
- )
150
- for k in item_list
151
- }
152
- )
153
- )
154
- self.num_items = len(item_list)
155
- self.items = item_list
156
-
157
- def add_to_mission_spec(self, mission_spec):
158
- pass
159
-
160
- def from_hero(self, info):
161
- return info["inventory"]
162
-
163
- def from_universal(self, obs):
164
- raise NotImplementedError(
165
- "from_universal not implemented in InventoryObservation"
166
- )
167
-
168
-
169
- class PlancraftBaseEnvSpec(HumanControlEnvSpec):
170
- def __init__(
171
- self,
172
- symbolic_action_space=False,
173
- symbolic_observation_space=False,
174
- max_episode_steps=2 * MINUTE,
175
- inventory: Sequence[dict] = (),
176
- preferred_spawn_biome: str = "plains",
177
- resolution=[260, 180],
178
- ):
179
- self.inventory = inventory
180
- self.preferred_spawn_biome = preferred_spawn_biome
181
- self.symbolic_action_space = symbolic_action_space
182
- self.symbolic_observation_space = symbolic_observation_space
183
-
184
- mode = "real"
185
- if symbolic_action_space:
186
- mode += "-symbolic-act"
187
- else:
188
- mode += "-real-act"
189
-
190
- if symbolic_observation_space:
191
- mode += "-symbolic-obs"
192
-
193
- if symbolic_action_space:
194
- cursor_size = 1
195
- else:
196
- cursor_size = 16
197
-
198
- name = f"plancraft-{mode}-v0"
199
- super().__init__(
200
- name=name,
201
- max_episode_steps=max_episode_steps,
202
- resolution=resolution,
203
- cursor_size_range=[cursor_size, cursor_size],
204
- )
205
-
206
- def create_agent_start(self) -> list[Handler]:
207
- base_agent_start_handlers = super().create_agent_start()
208
- return base_agent_start_handlers + [
209
- CustomInventoryAgentStart(self.inventory),
210
- handlers.PreferredSpawnBiome(self.preferred_spawn_biome),
211
- handlers.DoneOnDeath(),
212
- CraftingTableOnly(),
213
- ]
214
-
215
- def create_observables(self) -> list[TranslationHandler]:
216
- if self.symbolic_observation_space:
217
- return [
218
- handlers.POVObservation(self.resolution),
219
- InventoryObservation([item["slot"] for item in self.inventory]),
220
- ]
221
- return [handlers.POVObservation(self.resolution)]
222
-
223
- def create_server_world_generators(self) -> list[Handler]:
224
- # TODO the original biome forced is not implemented yet. Use this for now.
225
- return [handlers.DefaultWorldGenerator(force_reset=True)]
226
-
227
- def create_server_quit_producers(self) -> list[Handler]:
228
- return [
229
- handlers.ServerQuitFromTimeUp((self.max_episode_steps * mc.MS_PER_STEP)),
230
- handlers.ServerQuitWhenAnyAgentFinishes(),
231
- ]
232
-
233
- def create_server_initial_conditions(self) -> list[Handler]:
234
- return [
235
- handlers.TimeInitialCondition(allow_passage_of_time=False),
236
- handlers.SpawningInitialCondition(allow_spawning=True),
237
- ]
238
-
239
- def create_actionables(self) -> list[TranslationHandler]:
240
- """
241
- Symbolic env can move items around in the inventory using function
242
- Real env can use camera/keyboard
243
- """
244
- # Camera and mouse
245
- if self.symbolic_action_space:
246
- return [
247
- InventoryCommandAction(),
248
- SmeltCommandAction(),
249
- InventoryResetAction(),
250
- ]
251
- return [handlers.KeybasedCommandAction(v, v) for k, v in mc.KEYMAP.items()] + [
252
- handlers.CameraAction(),
253
- SmeltCommandAction(),
254
- InventoryResetAction(),
255
- ]
256
-
257
- def is_from_folder(self, folder: str) -> bool:
258
- return False
259
-
260
- def create_agent_handlers(self) -> list[Handler]:
261
- return []
262
-
263
- def create_mission_handlers(self):
264
- return []
265
-
266
- def create_monitors(self):
267
- return []
268
-
269
- def create_rewardables(self):
270
- return []
271
-
272
- def create_server_decorators(self) -> list[Handler]:
273
- return []
274
-
275
- def determine_success_from_rewards(self, rewards: list) -> bool:
276
- return False
277
-
278
- def get_docstring(self):
279
- return self.__class__.__doc__
280
-
281
-
282
- class RealPlancraft(_singleagent._SingleAgentEnv):
283
- def __init__(
284
- self,
285
- inventory: list[dict],
286
- preferred_spawn_biome="plains",
287
- symbolic_action_space=False,
288
- symbolic_observation_space=True,
289
- resolution=[512, 512],
290
- crop=True,
291
- ):
292
- # NOTE: crop is only supported for resolution 512x512 (default)
293
- self.crop = crop
294
- self.resolution = resolution
295
- env_spec = PlancraftBaseEnvSpec(
296
- symbolic_action_space=symbolic_action_space,
297
- symbolic_observation_space=symbolic_observation_space,
298
- preferred_spawn_biome=preferred_spawn_biome,
299
- inventory=inventory,
300
- resolution=resolution,
301
- )
302
- super(RealPlancraft, self).__init__(env_spec=env_spec)
303
- self.reset()
304
-
305
- def step(self, action: RealAction | dict):
306
- if not isinstance(action, dict):
307
- action = action.to_action_dict()
308
- obs, rew, done, info = super().step(action)
309
- if "pov" in obs and self.crop and self.resolution == [512, 512]:
310
- # crop at position x=174, y=170 with width=164 and height=173
311
- obs["pov"] = obs["pov"][174 : 174 + 164, 170 : 168 + 173]
312
- return obs, rew, done, info
313
-
314
- def fast_reset(self, new_inventory: list[dict]):
315
- super().step({"inventory_reset": new_inventory})