naldo commited on
Commit ·
d17cab1
1
Parent(s): 0e7d3e7
Bound Creative exploration and favor verifiable flat-world tasks
Browse files- tests/test_voyager_patch.py +24 -2
- voyager_stack.py +18 -5
- voyager_verification.py +12 -20
tests/test_voyager_patch.py
CHANGED
|
@@ -204,10 +204,10 @@ def check_curriculum_behavior(source):
|
|
| 204 |
agent.llm = Mock(side_effect=[SimpleNamespace(content="Task: Craft a stone pickaxe"),
|
| 205 |
SimpleNamespace(content="Task: Obtain a wood log")])
|
| 206 |
task, _ = agent.propose_next_ai_task(messages=messages, max_retries=2)
|
| 207 |
-
assert task == "
|
| 208 |
agent.llm = Mock(side_effect=[SimpleNamespace(content="Explain only") for _ in range(3)])
|
| 209 |
task, _ = agent.propose_next_ai_task(messages=[SimpleNamespace(content="Nearby blocks: dirt, stone")])
|
| 210 |
-
assert task == "
|
| 211 |
assert task_spec(task) not in {task_spec(old) for old in agent.completed_tasks + agent.failed_tasks}
|
| 212 |
agent.mode = "auto"
|
| 213 |
agent.render_system_message = Mock(return_value=SimpleNamespace(content="Creative"))
|
|
@@ -297,7 +297,29 @@ async function run(direction, mode) {
|
|
| 297 |
assert.equal(chats,0);
|
| 298 |
return goal;
|
| 299 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 300 |
(async()=>{
|
|
|
|
| 301 |
const g=await run({x:0.25,y:-0.4,z:-0.75},'creative');
|
| 302 |
assert(g.x>0 && g.z<0 && !('y' in g));
|
| 303 |
const v=await run({x:0,y:-1,z:0},'creative');
|
|
|
|
| 204 |
agent.llm = Mock(side_effect=[SimpleNamespace(content="Task: Craft a stone pickaxe"),
|
| 205 |
SimpleNamespace(content="Task: Obtain a wood log")])
|
| 206 |
task, _ = agent.propose_next_ai_task(messages=messages, max_retries=2)
|
| 207 |
+
assert task == "Place 1 stone"
|
| 208 |
agent.llm = Mock(side_effect=[SimpleNamespace(content="Explain only") for _ in range(3)])
|
| 209 |
task, _ = agent.propose_next_ai_task(messages=[SimpleNamespace(content="Nearby blocks: dirt, stone")])
|
| 210 |
+
assert task == "Place 1 stone" and agent.llm.call_count == 3
|
| 211 |
assert task_spec(task) not in {task_spec(old) for old in agent.completed_tasks + agent.failed_tasks}
|
| 212 |
agent.mode = "auto"
|
| 213 |
agent.render_system_message = Mock(return_value=SimpleNamespace(content="Creative"))
|
|
|
|
| 297 |
assert.equal(chats,0);
|
| 298 |
return goal;
|
| 299 |
}
|
| 300 |
+
async function bounded() {
|
| 301 |
+
let interval, timer;
|
| 302 |
+
const goals=[];
|
| 303 |
+
const context={Math,Number,Promise,Error,
|
| 304 |
+
GoalNearXZ: class {constructor(x,z){Object.assign(this,{x,z});}},
|
| 305 |
+
setInterval:fn=>(interval=fn,1), setTimeout:fn=>(timer=fn,2),
|
| 306 |
+
clearInterval:()=>{}, clearTimeout:()=>{}};
|
| 307 |
+
vm.createContext(context); vm.runInContext(source,context);
|
| 308 |
+
const bot={game:{gameMode:'creative'},entity:{position:{x:0,y:64,z:0}},
|
| 309 |
+
pathfinder:{setGoal:g=>goals.push(g)}};
|
| 310 |
+
const promise=context.exploreUntil(bot,{x:1,y:0,z:1},60,()=>false);
|
| 311 |
+
for(let i=0;i<12;i++) {
|
| 312 |
+
interval();
|
| 313 |
+
const goal=goals[goals.length-1];
|
| 314 |
+
if(goal===null) break;
|
| 315 |
+
assert(Math.hypot(goal.x,goal.z)<=32.00001);
|
| 316 |
+
bot.entity.position.x=goal.x; bot.entity.position.z=goal.z;
|
| 317 |
+
}
|
| 318 |
+
assert.equal(await promise,null);
|
| 319 |
+
assert.equal(goals[goals.length-1],null);
|
| 320 |
+
}
|
| 321 |
(async()=>{
|
| 322 |
+
await bounded();
|
| 323 |
const g=await run({x:0.25,y:-0.4,z:-0.75},'creative');
|
| 324 |
assert(g.x>0 && g.z<0 && !('y' in g));
|
| 325 |
const v=await run({x:0,y:-1,z:0},'creative');
|
voyager_stack.py
CHANGED
|
@@ -356,10 +356,11 @@ async function creativeGiveItem(bot, name, count = 1) {
|
|
| 356 |
const candidates = [[1,0],[-1,0],[0,1],[0,-1],[1,1],[1,-1],[-1,1],[-1,-1]];
|
| 357 |
[dx, dz] = candidates[Math.floor(Math.random() * candidates.length)];
|
| 358 |
}
|
| 359 |
-
if (bot.game && bot.game.gameMode === "creative") dy = 0;
|
| 360 |
-
|
| 361 |
-
maxTime = Math.min(maxTime, 120);
|
| 362 |
-
|
|
|
|
| 363 |
let explorationInterval;
|
| 364 |
let maxTimeTimeout;
|
| 365 |
const cleanUp = () => {
|
|
@@ -375,7 +376,19 @@ async function creativeGiveItem(bot, name, count = 1) {
|
|
| 375 |
resolve(result);
|
| 376 |
return;
|
| 377 |
}
|
| 378 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 379 |
const x = bot.entity.position.x + distance * dx;
|
| 380 |
const y = bot.entity.position.y + distance * dy;
|
| 381 |
const z = bot.entity.position.z + distance * dz;
|
|
|
|
| 356 |
const candidates = [[1,0],[-1,0],[0,1],[0,-1],[1,1],[1,-1],[-1,1],[-1,-1]];
|
| 357 |
[dx, dz] = candidates[Math.floor(Math.random() * candidates.length)];
|
| 358 |
}
|
| 359 |
+
if (bot.game && bot.game.gameMode === "creative") dy = 0;
|
| 360 |
+
|
| 361 |
+
maxTime = Math.min(maxTime, 120);
|
| 362 |
+
const start = {x: bot.entity.position.x, z: bot.entity.position.z};
|
| 363 |
+
return new Promise((resolve, reject) => {
|
| 364 |
let explorationInterval;
|
| 365 |
let maxTimeTimeout;
|
| 366 |
const cleanUp = () => {
|
|
|
|
| 376 |
resolve(result);
|
| 377 |
return;
|
| 378 |
}
|
| 379 |
+
let distance = Math.floor(Math.random() * 12 + 8);
|
| 380 |
+
if (bot.game && bot.game.gameMode === "creative") {
|
| 381 |
+
const travelled = Math.hypot(
|
| 382 |
+
bot.entity.position.x - start.x, bot.entity.position.z - start.z
|
| 383 |
+
);
|
| 384 |
+
const remaining = 32 - travelled;
|
| 385 |
+
if (remaining <= 1) {
|
| 386 |
+
cleanUp();
|
| 387 |
+
resolve(null);
|
| 388 |
+
return;
|
| 389 |
+
}
|
| 390 |
+
distance = Math.min(distance, remaining / Math.hypot(dx, dz));
|
| 391 |
+
}
|
| 392 |
const x = bot.entity.position.x + distance * dx;
|
| 393 |
const y = bot.entity.position.y + distance * dy;
|
| 394 |
const z = bot.entity.position.z + distance * dz;
|
voyager_verification.py
CHANGED
|
@@ -134,26 +134,11 @@ def choose_curriculum_task(response, completed, failed):
|
|
| 134 |
|
| 135 |
|
| 136 |
def fallback_curriculum_task(messages, completed, failed):
|
| 137 |
-
"""
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
name = name.strip().lower().replace(" ", "_")
|
| 143 |
-
if name != "none" and re.fullmatch(r"[a-z][a-z0-9_]*", name):
|
| 144 |
-
task = canonical_task(f"Mine 1 {name}")
|
| 145 |
-
if task:
|
| 146 |
-
try:
|
| 147 |
-
return validate_task(task, completed, failed)
|
| 148 |
-
except ValueError:
|
| 149 |
-
pass
|
| 150 |
-
for distance in (16, 24, 32):
|
| 151 |
-
for direction in ("north", "east", "south", "west"):
|
| 152 |
-
task = f"Explore {distance} blocks {direction}"
|
| 153 |
-
try:
|
| 154 |
-
return validate_task(task, completed, failed)
|
| 155 |
-
except ValueError:
|
| 156 |
-
pass
|
| 157 |
palette = (
|
| 158 |
"stone", "dirt", "grass_block", "cobblestone", "oak_planks", "spruce_planks",
|
| 159 |
"birch_planks", "sandstone", "bricks", "glass", "white_wool", "orange_wool",
|
|
@@ -170,6 +155,13 @@ def fallback_curriculum_task(messages, completed, failed):
|
|
| 170 |
return validate_task(task, completed, failed)
|
| 171 |
except ValueError:
|
| 172 |
pass
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 173 |
# The finite grammar can theoretically be exhausted. Keep Voyager alive;
|
| 174 |
# in practice hundreds of distinct safe objectives remain in the palette.
|
| 175 |
return "Explore 16 blocks north"
|
|
|
|
| 134 |
|
| 135 |
|
| 136 |
def fallback_curriculum_task(messages, completed, failed):
|
| 137 |
+
"""Choose a verifiable Creative placement before bounded exploration.
|
| 138 |
+
|
| 139 |
+
Nearby block names lack coordinates. In a flat world they mostly describe
|
| 140 |
+
the Y=63 floor, which mineBlock deliberately refuses to dig from Y=64.
|
| 141 |
+
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 142 |
palette = (
|
| 143 |
"stone", "dirt", "grass_block", "cobblestone", "oak_planks", "spruce_planks",
|
| 144 |
"birch_planks", "sandstone", "bricks", "glass", "white_wool", "orange_wool",
|
|
|
|
| 155 |
return validate_task(task, completed, failed)
|
| 156 |
except ValueError:
|
| 157 |
pass
|
| 158 |
+
for distance in (16, 24, 32):
|
| 159 |
+
for direction in ("north", "east", "south", "west"):
|
| 160 |
+
task = f"Explore {distance} blocks {direction}"
|
| 161 |
+
try:
|
| 162 |
+
return validate_task(task, completed, failed)
|
| 163 |
+
except ValueError:
|
| 164 |
+
pass
|
| 165 |
# The finite grammar can theoretically be exhausted. Keep Voyager alive;
|
| 166 |
# in practice hundreds of distinct safe objectives remain in the palette.
|
| 167 |
return "Explore 16 blocks north"
|