plancraft 0.4.2__py3-none-any.whl → 0.4.4__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.
@@ -323,12 +323,11 @@ class PlancraftEnvironment:
323
323
  # not enough
324
324
  if self.slot_empty(slot_from) or self.state[slot_from]["quantity"] < quantity:
325
325
  return
326
- # if craft slot - must take all
326
+ # if craft slot - move takes all
327
327
  if slot_from == 0 and self.state[slot_from]["quantity"] != quantity:
328
- return
328
+ quantity = self.state[slot_from]["quantity"]
329
329
 
330
330
  item = self.state[slot_from]
331
-
332
331
  # slot to is not empty or is the same type as item
333
332
  if self.slot_empty(slot_to):
334
333
  # add quantity to new slot
@@ -234,11 +234,13 @@ def get_plan(observation: dict):
234
234
 
235
235
  def decompose_subgoal(
236
236
  current_inventory: dict, plan_recipe, new_inventory: dict[str, int]
237
- ) -> list[str]:
237
+ ) -> tuple[list[str], dict[str, int], list[str]]:
238
238
  """
239
239
  For a given plan_recipe and inventory, output the list of action to craft recipe
240
240
  """
241
241
  subplan = []
242
+ # track the item type used in each action's from_slot
243
+ action_items_used = []
242
244
  new_inventory_counter = Counter(new_inventory)
243
245
  current_inventory_counter = get_inventory_counter(current_inventory)
244
246
  items_to_use_counter = current_inventory_counter - new_inventory_counter
@@ -264,6 +266,7 @@ def decompose_subgoal(
264
266
  action = SmeltAction(
265
267
  slot_from=from_slot, slot_to=free_slot, quantity=quantity
266
268
  )
269
+ action_items_used.append(current_inventory[from_slot]["type"])
267
270
  subplan.append(str(action))
268
271
 
269
272
  # update inventory to decrement quantity of item in from_slot and increment quantity of item in free_slot
@@ -280,7 +283,7 @@ def decompose_subgoal(
280
283
  if current_inventory[from_slot]["quantity"] <= 0:
281
284
  del current_inventory[from_slot]
282
285
 
283
- return subplan, current_inventory
286
+ return subplan, current_inventory, action_items_used
284
287
 
285
288
  elif isinstance(plan_recipe, ShapelessRecipe):
286
289
  crafting_slot = 1
@@ -314,6 +317,7 @@ def decompose_subgoal(
314
317
  slot_to=free_slot,
315
318
  quantity=current_inventory[crafting_slot]["quantity"],
316
319
  )
320
+ action_items_used.append(current_inventory[crafting_slot]["type"])
317
321
  subplan.append(str(action))
318
322
  current_inventory = update_inventory(
319
323
  current_inventory,
@@ -331,6 +335,7 @@ def decompose_subgoal(
331
335
  action = MoveAction(
332
336
  slot_from=from_slot, slot_to=crafting_slot, quantity=1
333
337
  )
338
+ action_items_used.append(current_inventory[from_slot]["type"])
334
339
  subplan.append(str(action))
335
340
  current_inventory = update_inventory(
336
341
  current_inventory, from_slot, crafting_slot, 1
@@ -366,6 +371,9 @@ def decompose_subgoal(
366
371
  slot_to=free_slot,
367
372
  quantity=current_inventory[inventory_position]["quantity"],
368
373
  )
374
+ action_items_used.append(
375
+ current_inventory[inventory_position]["type"]
376
+ )
369
377
  current_inventory = update_inventory(
370
378
  current_inventory,
371
379
  inventory_position,
@@ -411,6 +419,9 @@ def decompose_subgoal(
411
419
  "quantity"
412
420
  ],
413
421
  )
422
+ action_items_used.append(
423
+ current_inventory[inventory_position]["type"]
424
+ )
414
425
  current_inventory = update_inventory(
415
426
  current_inventory,
416
427
  inventory_position,
@@ -428,6 +439,7 @@ def decompose_subgoal(
428
439
  slot_to=inventory_position,
429
440
  quantity=1,
430
441
  )
442
+ action_items_used.append(current_inventory[from_slot]["type"])
431
443
  items_to_use_counter[item] -= 1
432
444
  added_item = True
433
445
  # update state of inventory
@@ -446,6 +458,7 @@ def decompose_subgoal(
446
458
  slot_to=free_slot,
447
459
  quantity=current_inventory[slot]["quantity"],
448
460
  )
461
+ action_items_used.append(current_inventory[slot]["type"])
449
462
  current_inventory = update_inventory(
450
463
  current_inventory,
451
464
  slot,
@@ -469,6 +482,7 @@ def decompose_subgoal(
469
482
  )
470
483
  )
471
484
  )
485
+ action_items_used.append(current_inventory[0]["type"])
472
486
  current_inventory = update_inventory(
473
487
  current_inventory, 0, free_slot, plan_recipe.result.count
474
488
  )
@@ -479,10 +493,10 @@ def decompose_subgoal(
479
493
  if current_inventory[i]["quantity"] <= 0:
480
494
  del current_inventory[i]
481
495
 
482
- return subplan, current_inventory
496
+ return subplan, current_inventory, action_items_used
483
497
 
484
498
 
485
- def get_subplans(observation: dict) -> tuple[list[list[str]], list]:
499
+ def get_subplans(observation: dict, return_items=False) -> tuple[list[list[str]], list]:
486
500
  current_inventory = copy.deepcopy(observation["inventory"])
487
501
  plan = get_plan(observation)
488
502
  # get action
@@ -490,10 +504,15 @@ def get_subplans(observation: dict) -> tuple[list[list[str]], list]:
490
504
  return [[str(StopAction())]], []
491
505
  # plan_recipe, new_inventory = plan[0]
492
506
  subplans = []
507
+ action_items_used = []
508
+
493
509
  # Calculate the subplans for each step in the plan
494
510
  for plan_recipe, new_inventory in plan:
495
- subplan, current_inventory = decompose_subgoal(
511
+ subplan, current_inventory, action_items = decompose_subgoal(
496
512
  current_inventory, plan_recipe, new_inventory
497
513
  )
498
514
  subplans.append(subplan)
515
+ action_items_used.append(action_items)
516
+ if return_items:
517
+ return subplans, plan, action_items_used
499
518
  return subplans, plan
@@ -110,17 +110,21 @@ def sample_recipes(
110
110
  return start_inputs, overall_exclude_set
111
111
 
112
112
 
113
- def remove_ancestor_items(target: str, inventory: dict[str, int]) -> dict[str, int]:
113
+ def remove_ancestor_items(
114
+ target: str, inventory: dict[str, int]
115
+ ) -> tuple[dict[str, int], list[tuple[str, int]]]:
114
116
  ancestors = set(get_ancestors(target))
115
117
  possible_items = set(inventory.keys())
116
118
  items_to_remove = list(ancestors.intersection(possible_items))
117
119
  num_items = random.randint(1, len(items_to_remove))
120
+ removed_items = []
118
121
  for item in random.sample(items_to_remove, num_items):
119
122
  count_to_remove = random.randint(1, inventory[item])
120
123
  inventory[item] -= count_to_remove
121
124
  if inventory[item] == 0:
122
125
  del inventory[item]
123
- return inventory
126
+ removed_items.append((item, count_to_remove))
127
+ return inventory, removed_items
124
128
 
125
129
 
126
130
  def construct_example(
@@ -142,9 +146,10 @@ def construct_example(
142
146
 
143
147
  # sample the recipe
144
148
  inventory, overall_exclude_set = sample_recipes(target, set())
149
+ removed_items = []
145
150
  if impossible:
146
151
  # if impossible then remove one or more items from the inventory
147
- inventory = remove_ancestor_items(
152
+ inventory, removed_items = remove_ancestor_items(
148
153
  target,
149
154
  inventory,
150
155
  )
@@ -158,7 +163,7 @@ def construct_example(
158
163
  while (optimal_path is not None and impossible) or (
159
164
  optimal_path is None and not impossible
160
165
  ):
161
- inventory = remove_ancestor_items(target, inventory)
166
+ inventory, removed_items = remove_ancestor_items(target, inventory)
162
167
  optimal_path = optimal_planner(target, inventory)
163
168
 
164
169
  # assign to slots
@@ -169,6 +174,7 @@ def construct_example(
169
174
  "target": target,
170
175
  "num_distractors": num_distractors,
171
176
  "impossible": impossible,
177
+ "missing_items": removed_items,
172
178
  }
173
179
  # either impossible and no path or not impossible and path exists
174
180
  assert (impossible and optimal_path is None) or (
plancraft/mcp.py ADDED
@@ -0,0 +1,429 @@
1
+ import base64
2
+ import csv
3
+ import os
4
+ import random
5
+ from collections.abc import AsyncIterator
6
+ from contextlib import asynccontextmanager
7
+ from dataclasses import dataclass, field
8
+ from typing import Any, Literal, Optional
9
+
10
+ from loguru import logger
11
+ from PIL import Image as PILImage
12
+ from plancraft.config import PlancraftExample
13
+ from plancraft.environment.actions import (
14
+ MoveAction,
15
+ SmeltAction,
16
+ StopAction,
17
+ )
18
+ from plancraft.environment.env import (
19
+ PlancraftEnvironment,
20
+ get_objective_str,
21
+ target_and_inventory_to_text_obs,
22
+ )
23
+ from plancraft.simple import get_plancraft_examples
24
+
25
+ from mcp.server.fastmcp import Context, FastMCP
26
+ from mcp.types import CallToolResult, ImageContent, TextContent
27
+
28
+
29
+ class PlancraftMCPWrapper:
30
+ def __init__(
31
+ self,
32
+ example: PlancraftExample,
33
+ max_steps: int = 30,
34
+ resolution: str = "high",
35
+ use_text_inventory: bool = True,
36
+ ):
37
+ self.max_steps = max_steps
38
+ # whether to convert the inventory to text observation
39
+ # if False, only the objective string is returned
40
+ self.use_text_inventory = use_text_inventory
41
+ self.current_step = 0
42
+ self.stopped = False
43
+ self.success = False
44
+ self.example = example
45
+ self.resolution = resolution
46
+ self.environment = PlancraftEnvironment(
47
+ example.slotted_inventory, resolution=self.resolution
48
+ )
49
+
50
+ def check_done(self, inventory: dict, target: str):
51
+ """
52
+ Check that target object is obtained
53
+ """
54
+ for slot, item in inventory.items():
55
+ # ensure the target is in the inventory (not in slot 0)
56
+ if target == item["type"] and slot != 0:
57
+ return True
58
+ return False
59
+
60
+ def step(
61
+ self, action: Optional[StopAction | MoveAction | SmeltAction] = None
62
+ ) -> tuple[dict[str, Any], bool]:
63
+ # Handle already stopped case
64
+ if self.stopped:
65
+ return (
66
+ {"text": "Plancraft environment is terminated"},
67
+ True,
68
+ )
69
+
70
+ # Handle initial step
71
+ if not action:
72
+ observation = self.environment.step()
73
+ observation["target"] = self.example.target
74
+ if self.use_text_inventory:
75
+ text = target_and_inventory_to_text_obs(
76
+ target=self.example.target, inventory=observation["inventory"]
77
+ )
78
+ else:
79
+ text = get_objective_str(self.example.target)
80
+ observation["text"] = text
81
+ return observation, self.stopped
82
+
83
+ # Handle max steps reached
84
+ if self.current_step > self.max_steps:
85
+ self.stopped = True
86
+ return (
87
+ {"text": f"Max steps ({self.max_steps}) reached"},
88
+ self.stopped,
89
+ )
90
+
91
+ self.current_step += 1
92
+ # Handle stop action
93
+ if isinstance(action, StopAction):
94
+ self.stopped = True
95
+ # success is True if example was truly impossible
96
+ self.success = self.example.impossible
97
+ observation = {
98
+ "text": "Plancraft environment is terminate due to stop action"
99
+ }
100
+ else:
101
+ observation = self.environment.step(action)
102
+ observation["target"] = self.example.target
103
+
104
+ # Generate text observation
105
+ if self.use_text_inventory:
106
+ text = target_and_inventory_to_text_obs(
107
+ target=self.example.target, inventory=observation["inventory"]
108
+ )
109
+ else:
110
+ text = get_objective_str(self.example.target)
111
+
112
+ observation["text"] = text
113
+
114
+ self.success = self.check_done(
115
+ observation["inventory"], self.example.target
116
+ )
117
+
118
+ return (
119
+ observation,
120
+ self.stopped,
121
+ )
122
+
123
+
124
+ @dataclass
125
+ class PlancraftContext:
126
+ """Context for the Plancraft environment."""
127
+
128
+ env: Optional[Any] = None
129
+ examples: list[PlancraftExample] = field(default_factory=list)
130
+
131
+ # Default environment settings
132
+ max_steps: int = 30
133
+ resolution: Literal["high", "low"] = "high"
134
+ use_text_inventory: bool = True
135
+ terminated: bool = False
136
+ use_images: bool = False
137
+
138
+ # history and statistics
139
+ history: list[dict] = field(default_factory=list)
140
+
141
+
142
+ @asynccontextmanager
143
+ async def app_lifespan(server: FastMCP) -> AsyncIterator[PlancraftContext]:
144
+ """Manage application lifecycle with type-safe context"""
145
+ # Initialize on startup
146
+ try:
147
+ logger.info("Starting up")
148
+ examples = get_plancraft_examples(split="train")
149
+ yield PlancraftContext(examples=examples)
150
+ finally:
151
+ # Cleanup on shutdown
152
+ logger.info("Shutting down")
153
+
154
+
155
+ # Pass lifespan to server
156
+ app = FastMCP("Plancraft", lifespan=app_lifespan)
157
+
158
+
159
+ @app.prompt()
160
+ def plancraft_environment_instructions() -> str:
161
+ return """You are crafting in Minecraft. You need to decide on the next action.
162
+
163
+ Crafting Grid: The crafting table is organized into a 3x3 grid. Each slot in the grid has a unique identifier:
164
+ - Top row: [A1] [A2] [A3]
165
+ - Middle row: [B1] [B2] [B3]
166
+ - Bottom row: [C1] [C2] [C3]
167
+
168
+ The output of the crafting process is placed in a designated output slot labeled [0] You cannot move or smelt items directly into slot [0]
169
+
170
+ Inventory Slots: The remaining inventory slots (outside of the crafting grid) are used for storing items. These slots are labeled as [I1] to [I36]
171
+
172
+ Constraints:
173
+ - You cannot move or smelt items into [0]
174
+ - If an item is not in slot [0] then the recipe is incorrect
175
+ - You need to move items from [0] to a free inventory slot to complete the crafting process"""
176
+
177
+
178
+ def observation_to_tool_result(
179
+ observation: dict,
180
+ terminated: bool = False,
181
+ success: bool = False,
182
+ add_instructions=False,
183
+ use_images=False,
184
+ ) -> CallToolResult:
185
+ content = []
186
+ if add_instructions:
187
+ instructions = plancraft_environment_instructions()
188
+ content.append(TextContent(text=instructions, type="text"))
189
+
190
+ text_content = observation["text"]
191
+ # Add success message if the task was completed successfully
192
+ if success:
193
+ text_content = f"SUCCESS! You have completed the task: {text_content}"
194
+ # Add termination message if the task was terminated but not successful
195
+ elif terminated:
196
+ text_content = f"Task terminated: {text_content}"
197
+
198
+ content.append(TextContent(text=text_content, type="text"))
199
+
200
+ if use_images:
201
+ # numpy array to PIL
202
+ pil_image = PILImage.fromarray(observation["image"])
203
+ # Save the image to a BytesIO buffer in PNG format
204
+ import io
205
+
206
+ buffer = io.BytesIO()
207
+ pil_image.save(buffer, format="PNG")
208
+ buffer.seek(0)
209
+
210
+ # Encode the properly formatted PNG data
211
+ base64_data = base64.b64encode(buffer.getvalue()).decode("utf-8")
212
+ content.append(
213
+ ImageContent(type="image", data=base64_data, mimeType="image/png")
214
+ )
215
+ return CallToolResult(
216
+ content=content,
217
+ )
218
+
219
+
220
+ def add_action_to_history(
221
+ action: MoveAction | SmeltAction | StopAction, result: CallToolResult, ctx: Context
222
+ ) -> None:
223
+ """Add action call to history - storing only text content for simplicity"""
224
+ # Extract only the text content from the result
225
+ text_content = ""
226
+ for content in result.content:
227
+ if hasattr(content, "text"):
228
+ text_content = content.text
229
+ break
230
+ ctx.request_context.lifespan_context.history.append(
231
+ {"action": str(action), "observation": text_content}
232
+ )
233
+
234
+
235
+ def save_result_if_terminated(environment, terminated: bool, ctx: Context) -> None:
236
+ """Helper function to save results if the environment is terminated"""
237
+ if terminated:
238
+ logger.info(f"Task completed or terminated in {environment.current_step} steps")
239
+
240
+ result_data = {
241
+ "example_id": environment.example.id,
242
+ "success": environment.success,
243
+ "steps": environment.current_step,
244
+ "history": ctx.request_context.lifespan_context.history.copy(),
245
+ }
246
+
247
+ # save the result data to a CSV file
248
+ pwd = os.path.dirname(os.path.abspath(__file__))
249
+ os.makedirs("results", exist_ok=True)
250
+ result_file = os.path.join(pwd, "results", "results.csv")
251
+ with open(result_file, mode="a", newline="") as f:
252
+ writer = csv.DictWriter(f, fieldnames=result_data.keys())
253
+ if f.tell() == 0:
254
+ writer.writeheader()
255
+ writer.writerow(result_data)
256
+
257
+ # Reset history after storing results
258
+ ctx.request_context.lifespan_context.history = []
259
+
260
+
261
+ def correct_slot_format(slot: str):
262
+ # helper function to correct the slot format
263
+ # Claude seems unable to generate slots with brackets
264
+ if "[" not in slot and "]" not in slot:
265
+ return f"[{slot}]"
266
+
267
+
268
+ @app.tool(
269
+ name="smelt",
270
+ description="Smelt items in the Plancraft environment. You must specify the slot to smelt from and the slot to smelt to and quantity.",
271
+ )
272
+ def smelt(from_slot: str, to_slot: str, quantity: int, ctx: Context) -> CallToolResult:
273
+ """Smelt items in the Plancraft environment. You must specify the slot to smelt from and the slot to smelt to and quantity."""
274
+ try:
275
+ environment = ctx.request_context.lifespan_context.env
276
+ use_images = ctx.request_context.lifespan_context.use_images
277
+
278
+ if environment.stopped:
279
+ content = "Plancraft environment is terminated, first call start_new_task to start a new task"
280
+ return CallToolResult(
281
+ content=[TextContent(text=content, type="text")], isError=True
282
+ )
283
+
284
+ smelt_action = SmeltAction(
285
+ slot_from=correct_slot_format(from_slot),
286
+ slot_to=correct_slot_format(to_slot),
287
+ quantity=quantity,
288
+ )
289
+ obs, terminated = environment.step(smelt_action)
290
+
291
+ # Generate the result with appropriate success/termination messages
292
+ result = observation_to_tool_result(
293
+ obs,
294
+ terminated=terminated,
295
+ success=environment.success,
296
+ use_images=use_images,
297
+ )
298
+ add_action_to_history(smelt_action, result, ctx)
299
+ logger.info(f"Step {environment.current_step}: {obs['text']}")
300
+
301
+ # Save result if the task is terminated
302
+ save_result_if_terminated(environment, terminated, ctx)
303
+
304
+ return result
305
+ except Exception as e:
306
+ return CallToolResult(
307
+ content=[TextContent(text=str(e), type="text")], isError=True
308
+ )
309
+
310
+
311
+ @app.tool(
312
+ name="move",
313
+ description="Move items in the Plancraft environment. You must specify the slot to move from and the slot to move to and quantity.",
314
+ )
315
+ def move(from_slot: str, to_slot: str, quantity: int, ctx: Context) -> CallToolResult:
316
+ """
317
+ Move items in the Plancraft environment. You must specify the slot to move from and the slot to move to and quantity.
318
+ """
319
+ try:
320
+ environment = ctx.request_context.lifespan_context.env
321
+ use_images = ctx.request_context.lifespan_context.use_images
322
+ if environment.stopped:
323
+ content = "Plancraft environment is terminated, first call start_new_task to start a new task"
324
+ return CallToolResult(
325
+ content=[TextContent(text=content, type="text")], isError=True
326
+ )
327
+
328
+ move_action = MoveAction(
329
+ slot_from=correct_slot_format(from_slot),
330
+ slot_to=correct_slot_format(to_slot),
331
+ quantity=quantity,
332
+ )
333
+ obs, terminated = environment.step(move_action)
334
+
335
+ # Generresult with appropriate success/termination messages
336
+ result = observation_to_tool_result(
337
+ obs,
338
+ terminated=terminated,
339
+ success=environment.success,
340
+ use_images=use_images,
341
+ )
342
+ add_action_to_history(move_action, result, ctx)
343
+ logger.info(f"Step {environment.current_step}: {obs['text']}")
344
+
345
+ # Save result if the task is terminated
346
+ save_result_if_terminated(environment, terminated, ctx)
347
+
348
+ return result
349
+
350
+ except Exception as e:
351
+ return CallToolResult(
352
+ content=[TextContent(text=str(e), type="text")], isError=True
353
+ )
354
+
355
+
356
+ @app.tool(
357
+ name="impossible",
358
+ description="Declare the current task impossible. This will end the current task.",
359
+ )
360
+ def impossible_task(reason: str, ctx: Context) -> CallToolResult:
361
+ """Declare the current task impossible. This will end the current task."""
362
+ try:
363
+ environment = ctx.request_context.lifespan_context.env
364
+ use_images = ctx.request_context.lifespan_context.use_images
365
+ stop_action = StopAction(reason=reason)
366
+ obs, terminated = environment.step(stop_action)
367
+
368
+ # Generate the ith appropriate success/termination messages
369
+ # For impossible action, it's successful if the task was truly impossible
370
+ result = observation_to_tool_result(
371
+ obs,
372
+ terminated=terminated,
373
+ success=environment.success,
374
+ use_images=use_images,
375
+ )
376
+ add_action_to_history(stop_action, result, ctx)
377
+ logger.info(f"Step {environment.current_step}: {obs['text']}")
378
+
379
+ # Save result if the task is terminated
380
+ save_result_if_terminated(environment, terminated, ctx)
381
+
382
+ return result
383
+ except Exception as e:
384
+ return CallToolResult(
385
+ content=[TextContent(text=str(e), type="text")], isError=True
386
+ )
387
+
388
+
389
+ @app.tool(
390
+ name="start_plancraft_task",
391
+ description="Start a new Plancraft environment (default use_images=False)",
392
+ )
393
+ def start_plancraft(use_images: bool, ctx: Context) -> CallToolResult:
394
+ """Tool that uses initialized resources"""
395
+ # Check if there's an existing environment and if it wasn't terminated yet
396
+ current_env = ctx.request_context.lifespan_context.env
397
+ if current_env and not current_env.stopped:
398
+ # Save the current environment state in results before discarding
399
+ logger.info("Discarding existing incomplete task")
400
+ # Reset history when starting a new task
401
+ ctx.request_context.lifespan_context.history = []
402
+
403
+ ctx.request_context.lifespan_context.use_images = use_images
404
+
405
+ random_idx = random.randint(
406
+ 0, len(ctx.request_context.lifespan_context.examples) - 1
407
+ )
408
+ example: PlancraftExample = ctx.request_context.lifespan_context.examples[
409
+ random_idx
410
+ ]
411
+ # initialize the environment
412
+ env = PlancraftMCPWrapper(
413
+ example=example,
414
+ max_steps=ctx.request_context.lifespan_context.max_steps,
415
+ resolution=ctx.request_context.lifespan_context.resolution,
416
+ use_text_inventory=ctx.request_context.lifespan_context.use_text_inventory,
417
+ )
418
+ logger.info(f"Environment initialized with example {example.id}")
419
+ ctx.request_context.lifespan_context.env = env
420
+
421
+ obs, _ = env.step()
422
+ logger.info(f"Step {env.current_step}: {obs['text']}")
423
+
424
+ result = observation_to_tool_result(obs, add_instructions=True)
425
+ return result
426
+
427
+
428
+ if __name__ == "__main__":
429
+ app.run()
File without changes
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plancraft
3
- Version: 0.4.2
3
+ Version: 0.4.4
4
4
  Summary: Plancraft: an evaluation dataset for planning with LLM agents
5
5
  License: MIT License
6
6
 
@@ -2,26 +2,25 @@ plancraft/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
2
  plancraft/config.py,sha256=oyn8I_k0Slh-Nyg2javomFertZ5ZHiY_ndAVqfJYQvQ,4010
3
3
  plancraft/evaluator.py,sha256=UQujiltf88rCnbNwoglM5tJe5gW9XASew-jLaEbtJZo,15525
4
4
  plancraft/generate_dataset.py,sha256=DlrU-PmvWqSNJD1g1-8Lpb8n3N-Ogw3rje1nrRzjGKs,2382
5
+ plancraft/mcp.py,sha256=0DHYAtdgl-CD_oqKpWnTF7L1RdegPK1wU8Naq6dtVIU,15165
5
6
  plancraft/simple.py,sha256=7D_SVT2sbXLnHA98P2E_nxV5VPBKbNpij4hB2ArURxA,7329
7
+ plancraft/simple_evaluator.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
8
  plancraft/utils.py,sha256=hCE1oQ-77Me39Vo-sCL7iZPdO-WWYZnBjP41lZWRi20,6339
7
- plancraft/data/test.curriculum.json,sha256=_mqIqwcmRCcgVA5fOKK27-y3sJdsW86nEqa6ATdyTeA,875349
8
9
  plancraft/data/test.json,sha256=4jWfYMAVuZCFmGB4iZJAjlh9_8jXECdaGp8xn7_tAM4,1317131
9
10
  plancraft/data/test.small.easy.json,sha256=5NZEJ2PqIgmHQecJOIVQyM1D6GFKyJq7GVmgRudaqQk,189304
10
11
  plancraft/data/test.small.json,sha256=eULAG1rdolRMXPrecV-7YoDIheKGyIT5MVpWdISV0wg,270089
11
- plancraft/data/train.curriculum.json,sha256=eOfGusDtGG1BBvTJdzjUdhTHVAfFro_vGyYgKzZuxvE,1787758
12
12
  plancraft/data/train.json,sha256=asZIFnkBdgupPKRXacM4J0Ngt21B2BrMT6oPgFA96HI,2697710
13
- plancraft/data/val.curriculum.json,sha256=fso4S2g0kqMk7Ehz6ObQmJHk4wq0gAD2YQm0R8Nb95E,866889
14
13
  plancraft/data/val.json,sha256=IToAiaqUNQi_xhX1bzmInuskLaT7C2ryQjP-CZkzL24,1304403
15
14
  plancraft/data/val.small.easy.json,sha256=9zEmqepjXG2NIp88xnFqOCkwsUsku3HEwHoQGxgTr6U,190252
16
15
  plancraft/data/val.small.json,sha256=76E9EFaljDQyAokg97e-IblvcOe6KbrdKkXvRxhhkgo,237653
17
16
  plancraft/environment/__init__.py,sha256=XFsFny4lH195AwAmL-WeCaF9ZCMgc7IgXIwhQ8FTdgE,505
18
17
  plancraft/environment/actions.py,sha256=Pub21caxM5iZ9IaX-ny1-xxr_peJIwwV_QAx3BVSry0,11551
19
- plancraft/environment/env.py,sha256=A4532st7JFBYBF_Nh0CEEi3ZTLJAeaB3t9PAIVSemj0,16390
18
+ plancraft/environment/env.py,sha256=B7VpIMcQKITyDmkHgBoR4KbmxmM1b4A6Y-1_b90EMXo,16428
20
19
  plancraft/environment/items.py,sha256=Z9rhSyVDEoHF1pxRvhyiT94tyQJaWHi3wUHVcamz82o,221
21
- plancraft/environment/planner.py,sha256=uIOJjIoyT_4pxeWeTKb8BkLJyKZG0-AMoEOkZs6Ua9A,19340
20
+ plancraft/environment/planner.py,sha256=-ZN6MSQxMTNfexa6ixzzFKK-qBSB4jbxOJLtknnHju8,20437
22
21
  plancraft/environment/prompts.py,sha256=NU9YHAz3id-IgaukQvEi5uLlpEstpE5_Hccvvq1At2Y,6950
23
22
  plancraft/environment/recipes.py,sha256=0vwzOU86eZmGN2EpZVSIvzxpx0AOBWNPxTtAOFBN2A0,19570
24
- plancraft/environment/sampler.py,sha256=F9_lI6cyg79HkeSLJVyvh9yZkT-B9PlJWyqaoAfAhwY,7502
23
+ plancraft/environment/sampler.py,sha256=BworSMWQ-TLbV9068tkNOdo4ZLP-UDox6Laeb4Weu9k,7723
25
24
  plancraft/environment/search.py,sha256=z31eEwQBY7WJaYVBEEwulFS8P3h1Nwo1Th9BaCTxk5M,2085
26
25
  plancraft/environment/assets/constants.json,sha256=kyOIOh82CTTMMGEIS60k5k6M-6fkEmYDoGAnvi3Zx5k,1379016
27
26
  plancraft/environment/assets/minecraft_font.ttf,sha256=AzoK9cgggXwjFPHtIO7uz-YaDrminl3nvB-VsaTvTAk,60992
@@ -1924,7 +1923,7 @@ plancraft/models/generators.py,sha256=7COMLjjx_HbTWJqINNLqqExQv7gLikfLTViacAdSt5
1924
1923
  plancraft/models/oracle.py,sha256=f-0KWlBuHy6wcxmDsxM3MQ_QwfBstzfbA26mlk1MgLA,1657
1925
1924
  plancraft/models/utils.py,sha256=xgkP5jqCeFfkKe3Xd4ZYfTqiEJ-dA-qgFAC-J35ub3E,4029
1926
1925
  plancraft/train/dataset.py,sha256=oFqEd4LG9oEQ-71teh0Wf7-jJbtybT2ZibfM2bBdBkM,5474
1927
- plancraft-0.4.2.dist-info/METADATA,sha256=OMyTZhI_6V_7geLfA1i7g3sJTB9dh6LKZB76Ys3aqLY,12391
1928
- plancraft-0.4.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
1929
- plancraft-0.4.2.dist-info/licenses/LICENSE,sha256=YGR8ehDB4t-T-lOQKMfKNR-2zsOU7E3E5NA8t25HKE0,1070
1930
- plancraft-0.4.2.dist-info/RECORD,,
1926
+ plancraft-0.4.4.dist-info/METADATA,sha256=Yjro1BL1G5QoDlC8v_RStAtPZykvO5pDOwdaHV_8U-c,12391
1927
+ plancraft-0.4.4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
1928
+ plancraft-0.4.4.dist-info/licenses/LICENSE,sha256=YGR8ehDB4t-T-lOQKMfKNR-2zsOU7E3E5NA8t25HKE0,1070
1929
+ plancraft-0.4.4.dist-info/RECORD,,