Files
Mortdecai/training/scripts/generate_new_tool_examples.py
T
Seth 5b28002001 0.6.0 training session: Oracle Bot, RL combat, Mind's Eye, multilingual pipeline
Major changes from this session:

Training:
- 0.6.0 training running: 9B on steel141 3090 Ti, 27B on rented H100 NVL
- 7,256 merged training examples (up from 3,183)
- New training data: failure modes (85), midloop messaging (27),
  prompt injection defense (29), personality (32), gold from quarantine
  bank (232), new tool examples (30), claude's own experience (10)
- All training data RCON-validated at 100% pass rate
- Bake-off: gemma3:27b 66%, qwen3.5:27b 61%, translategemma:27b 56%

Oracle Bot (Mind's Eye):
- Invisible spectator bot (mineflayer) streams world state via WebSocket
- HTML5 Canvas frontend at mind.mortdec.ai
- Real-time tool trace visualization with expandable entries
- Streaming model tokens during inference
- Gateway integration: fire-and-forget POST /trace on every tool call

Reinforcement Learning:
- Gymnasium environment wrapping mineflayer bot (minecraft_env.py)
- PPO training via Stable Baselines3 (10K param policy network)
- Behavioral cloning pretraining (97.5% accuracy on expert policy)
- Infinite training loop with auto-restart and checkpoint resume
- Bot learns combat, survival, navigation from raw experience

Bot Army:
- 8-soldier marching formation with autonomous combat
- Combat bots using mineflayer-pvp, pathfinder, armor-manager
- Multilingual prayer bots via translategemma:27b (18 languages)
- Frame-based AI architecture: LLM planner + reactive micro-scripts

Infrastructure:
- Fixed mattpc.sethpc.xyz billing gateway (API key + player list parser)
- Billing gateway now tracks all LAN traffic (LAN auto-auth)
- Gateway fallback for empty god-mode responses
- Updated mortdec.ai landing page

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 20:22:50 -04:00

330 lines
24 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""
generate_new_tool_examples.py — Generate training examples for under-represented tools.
Covers: world.render, server.config, training.save, user.ask, and additional
world.scan_area + world.redstone_trace patterns.
Output: data/raw/new_tool_training.jsonl
"""
import json
import random
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent.parent
OUTPUT = ROOT / "data" / "raw" / "new_tool_training.jsonl"
SYSTEM_PROMPT = """/no_think
You are Mortdecai, a Minecraft 1.21 AI for a Paper server with FAWE, WorldGuard, CoreProtect, EssentialsX, Vault, LuckPerms.
TOOLS: rcon.execute, minecraft.lookup, plugin.docs_lookup, world.player_info, world.server_state, world.nearby_entities, world.scan_area, world.redstone_trace, world.render, server.config, memory.read, memory.write, journal.read, journal.write, log.query, user.ask, script.write, script.validate, script.execute, script.read, script.list, script.delete, script.schedule, training.save
To call a tool, respond with:
<tool_call>
{"name": "tool_name", "arguments": {...}}
</tool_call>
After all tool calls, respond with final JSON: {"commands": [...], "message": "...", "reasoning": "..."}"""
PLAYERS = ["slingshooter08", "SwiftWolf", "DarkWolf", "BraveWolf", "CraftMaster99", "RedstoneWiz"]
def tc(name, args):
"""Format a tool_call block."""
return f'<tool_call>\n{json.dumps({"name": name, "arguments": args})}\n</tool_call>'
def tr(result):
"""Format a tool result."""
return json.dumps(result, ensure_ascii=True)
def final(commands=None, message=None, reasoning=""):
return json.dumps({"commands": commands or [], "message": message, "reasoning": reasoning})
# ── world.render examples ──────────────────────────────────────────
def render_build_inspection():
p = random.choice(PLAYERS)
return {"messages": [
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": f"Player {p}: sudo show me what my house looks like from above"},
{"role": "assistant", "content": tc("world.player_info", {"player": p})},
{"role": "tool", "content": tr({"ok": True, "player": p, "position": {"x": 50, "y": 68, "z": -30}, "gamemode": "survival"})},
{"role": "assistant", "content": tc("world.render", {"x1": 40, "y1": 60, "z1": -40, "x2": 60, "y2": 80, "z2": -20, "mode": "top_down", "y_level": 68})},
{"role": "tool", "content": tr({"ok": True, "render": "....WWWWWW....\n...W......W...\n..W..DD..W...\n..W......W...\n..W......W...\n...WWWWWW....\n\nLegend: W=oak_planks D=oak_door .=air", "mode": "top_down", "blocks_rendered": 24})},
{"role": "assistant", "content": tc("rcon.execute", {"command": f'tellraw {p} {{"text":"Here is a top-down view of your house at Y=68. It is a rectangular oak plank structure with a door on the south side.","color":"green"}}'})},
{"role": "tool", "content": tr({"success": True, "result": ""})},
{"role": "assistant", "content": final([], None, "Rendered player's house from top-down view at their Y level. Communicated the layout via tellraw.")},
]}
def render_cross_section():
p = random.choice(PLAYERS)
return {"messages": [
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": f"Player {p}: sudo can you see how deep my mine goes?"},
{"role": "assistant", "content": tc("world.player_info", {"player": p})},
{"role": "tool", "content": tr({"ok": True, "player": p, "position": {"x": -100, "y": 20, "z": 200}, "gamemode": "survival"})},
{"role": "assistant", "content": tc("world.render", {"x1": -100, "y1": -60, "z1": 195, "x2": -100, "y2": 68, "z2": 205, "mode": "cross_z"})},
{"role": "tool", "content": tr({"ok": True, "render": "SSSSSSSSS\nSSSS.SSSS\nSSS...SSS\nSS.....SS\nS.......S\n.........\n.T....T..\n.........\nSSSSSSSSS\n\nLegend: S=stone .=air T=torch", "mode": "cross_z", "blocks_rendered": 45})},
{"role": "assistant", "content": final([], None, "Rendered cross-section of player's mine. It extends from Y=68 surface down approximately 40 blocks with torches placed along the shaft.")},
]}
def render_redstone_layout():
p = "RedstoneWiz"
return {"messages": [
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": f"Player {p}: sudo render my redstone circuit please"},
{"role": "assistant", "content": tc("world.player_info", {"player": p})},
{"role": "tool", "content": tr({"ok": True, "player": p, "position": {"x": 10, "y": 64, "z": 10}, "gamemode": "creative"})},
{"role": "assistant", "content": tc("world.scan_area", {"center": {"x": 10, "y": 64, "z": 10}, "radius": 15, "filter": "redstone"})},
{"role": "tool", "content": tr({"ok": True, "blocks": [{"pos": [5, 64, 10], "type": "lever"}, {"pos": [6, 64, 10], "type": "redstone_wire"}, {"pos": [7, 64, 10], "type": "redstone_wire"}, {"pos": [8, 64, 10], "type": "repeater"}, {"pos": [9, 64, 10], "type": "redstone_wire"}, {"pos": [10, 64, 10], "type": "redstone_lamp"}], "count": 6})},
{"role": "assistant", "content": tc("world.render", {"x1": 3, "y1": 63, "z1": 8, "x2": 12, "y2": 65, "z2": 12, "mode": "top_down", "y_level": 64})},
{"role": "tool", "content": tr({"ok": True, "render": "...........\n..L-RR>R-O.\n...........\n\nLegend: L=lever -=redstone_wire >=repeater O=lamp .=air", "mode": "top_down", "blocks_rendered": 6})},
{"role": "assistant", "content": final([], None, "Scanned and rendered redstone circuit: lever → 2 wire → repeater → 1 wire → lamp. Linear circuit running east at Y=64.")},
]}
# ── server.config examples ──────────────────────────────────────────
def config_read_gamerules():
p = random.choice(PLAYERS)
return {"messages": [
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": f"Player {p}: sudo what are the current gamerules?"},
{"role": "assistant", "content": tc("server.config", {"action": "read", "setting": "keepInventory"})},
{"role": "tool", "content": tr({"ok": True, "setting": "keepInventory", "value": "false"})},
{"role": "assistant", "content": tc("server.config", {"action": "read", "setting": "mobGriefing"})},
{"role": "tool", "content": tr({"ok": True, "setting": "mobGriefing", "value": "true"})},
{"role": "assistant", "content": tc("server.config", {"action": "read", "setting": "doDaylightCycle"})},
{"role": "tool", "content": tr({"ok": True, "setting": "doDaylightCycle", "value": "true"})},
{"role": "assistant", "content": final([], None, "Checked key gamerules: keepInventory=false (items drop on death), mobGriefing=true (mobs can damage terrain), doDaylightCycle=true (time passes normally).")},
]}
def config_set_keepinventory():
p = random.choice(PLAYERS)
return {"messages": [
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": f"Player {p}: sudo turn on keep inventory please"},
{"role": "assistant", "content": tc("server.config", {"action": "read", "setting": "keepInventory"})},
{"role": "tool", "content": tr({"ok": True, "setting": "keepInventory", "value": "false"})},
{"role": "assistant", "content": tc("server.config", {"action": "set", "setting": "keepInventory", "value": "true"})},
{"role": "tool", "content": tr({"ok": True, "setting": "keepInventory", "value": "true", "previous": "false"})},
{"role": "assistant", "content": final(["gamerule keepInventory true"], None, "Checked keepInventory was false, then enabled it. Players will now keep items on death.")},
]}
def config_difficulty():
p = random.choice(PLAYERS)
return {"messages": [
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": f"Player {p}: sudo make the game harder"},
{"role": "assistant", "content": tc("server.config", {"action": "read", "setting": "difficulty"})},
{"role": "tool", "content": tr({"ok": True, "setting": "difficulty", "value": "normal"})},
{"role": "assistant", "content": tc("server.config", {"action": "set", "setting": "difficulty", "value": "hard"})},
{"role": "tool", "content": tr({"ok": True, "setting": "difficulty", "value": "hard", "previous": "normal"})},
{"role": "assistant", "content": final(["difficulty hard"], None, "Changed difficulty from normal to hard. Zombies can break doors, hunger can kill, and mobs deal more damage.")},
]}
def config_worldborder():
p = random.choice(PLAYERS)
return {"messages": [
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": f"Player {p}: sudo set the world border to 5000 blocks"},
{"role": "assistant", "content": tc("server.config", {"action": "read", "setting": "worldborder"})},
{"role": "tool", "content": tr({"ok": True, "setting": "worldborder", "value": "60000000"})},
{"role": "assistant", "content": tc("server.config", {"action": "set", "setting": "worldborder", "value": "5000"})},
{"role": "tool", "content": tr({"ok": True, "setting": "worldborder", "value": "5000", "previous": "60000000"})},
{"role": "assistant", "content": final(["worldborder set 5000"], None, "Set world border from default (60M) to 5000 blocks diameter. Players cannot travel beyond 2500 blocks from center.")},
]}
# ── user.ask examples ──────────────────────────────────────────────
def user_ask_ambiguous_kill():
p = random.choice(PLAYERS)
return {"messages": [
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": f"Player {p}: sudo kill that guy"},
{"role": "assistant", "content": tc("world.nearby_entities", {"player": p, "radius": 30})},
{"role": "tool", "content": tr({"ok": True, "entities": [{"type": "player", "name": "SwiftWolf", "distance": 8}, {"type": "player", "name": "DarkWolf", "distance": 15}, {"type": "zombie", "count": 3, "nearest_distance": 12}]})},
{"role": "assistant", "content": tc("user.ask", {"player": p, "question": "There are 2 players nearby: SwiftWolf (8 blocks) and DarkWolf (15 blocks), plus 3 zombies. Who did you mean by 'that guy'?"})},
{"role": "tool", "content": tr({"ok": True, "response": "the zombies not the players lol"})},
{"role": "assistant", "content": tc("rcon.execute", {"command": "kill @e[type=minecraft:zombie,distance=..30]"})},
{"role": "tool", "content": tr({"success": True, "result": "Killed 3 entities"})},
{"role": "assistant", "content": final(["kill @e[type=minecraft:zombie,distance=..30]"], None, "Ambiguous 'kill that guy' — checked nearby entities, found both players and zombies. Asked for clarification. Player meant the zombies. Killed all zombies within 30 blocks.")},
]}
def user_ask_destructive_fill():
p = random.choice(PLAYERS)
return {"messages": [
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": f"Player {p}: sudo fill this whole area with lava"},
{"role": "assistant", "content": tc("world.player_info", {"player": p})},
{"role": "tool", "content": tr({"ok": True, "player": p, "position": {"x": 30, "y": 65, "z": -20}, "gamemode": "survival"})},
{"role": "assistant", "content": tc("world.nearby_entities", {"player": p, "radius": 50})},
{"role": "tool", "content": tr({"ok": True, "entities": [{"type": "player", "name": "BraveWolf", "distance": 20}, {"type": "cow", "count": 5, "nearest_distance": 10}]})},
{"role": "assistant", "content": tc("user.ask", {"player": p, "question": "Filling with lava is destructive and BraveWolf is 20 blocks away. How large an area? And should I warn BraveWolf first?"})},
{"role": "tool", "content": tr({"ok": True, "response": "just a 5x5 pit right here, and yeah warn them"})},
{"role": "assistant", "content": tc("rcon.execute", {"command": f'tellraw BraveWolf {{"text":"Warning: {p} is filling a small area with lava near you. Stay clear!","color":"red"}}'})},
{"role": "tool", "content": tr({"success": True, "result": ""})},
{"role": "assistant", "content": tc("rcon.execute", {"command": "fill 28 60 -22 32 64 -18 minecraft:lava"})},
{"role": "tool", "content": tr({"success": True, "result": "Successfully filled 125 blocks"})},
{"role": "assistant", "content": final(["fill 28 60 -22 32 64 -18 minecraft:lava"], None, "Destructive lava fill — asked for clarification on scope. Player wanted 5x5 pit. Warned nearby player BraveWolf first, then filled.")},
]}
def user_ask_ban_request():
p = random.choice(PLAYERS)
return {"messages": [
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": f"Player {p}: sudo ban SwiftWolf he was griefing"},
{"role": "assistant", "content": tc("log.query", {"type": "actions", "player": "SwiftWolf", "limit": 10})},
{"role": "tool", "content": tr({"ok": True, "results": [{"type": "action", "player": "SwiftWolf", "detail": "placed TNT at (100, 65, 200)", "age": "3m ago"}, {"type": "action", "player": "SwiftWolf", "detail": "broke oak_planks at (102, 66, 201)", "age": "4m ago"}], "count": 2})},
{"role": "assistant", "content": tc("user.ask", {"player": p, "question": "I see SwiftWolf placed TNT and broke some oak planks recently. Are you sure you want a permanent ban? Other options: tempban (hours), kick, or jail."})},
{"role": "tool", "content": tr({"ok": True, "response": "just kick him for now and warn him"})},
{"role": "assistant", "content": tc("rcon.execute", {"command": "kick SwiftWolf Griefing warning: Do not place TNT near player builds."})},
{"role": "tool", "content": tr({"success": True, "result": "Kicked SwiftWolf"})},
{"role": "assistant", "content": final(["kick SwiftWolf Griefing warning: Do not place TNT near player builds."], None, "Investigated griefing claim — logs confirmed TNT placement and block breaking. Asked about severity preference. Player chose kick with warning instead of ban.")},
]}
# ── training.save examples ──────────────────────────────────────────
def training_save_syntax_discovery():
p = random.choice(PLAYERS)
return {"messages": [
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": f"Player {p}: sudo give me a colored leather helmet"},
{"role": "assistant", "content": tc("minecraft.lookup", {"query": "leather armor color dye command 1.21"})},
{"role": "tool", "content": tr({"ok": True, "results": [{"title": "Dyed Armor", "text": "In 1.21, use: give @p leather_helmet[dyed_color=16711680] for red. Color is decimal RGB."}]})},
{"role": "assistant", "content": tc("rcon.execute", {"command": f"give {p} minecraft:leather_helmet[dyed_color=16711680] 1"})},
{"role": "tool", "content": tr({"success": False, "result": "Invalid or unknown argument"})},
{"role": "assistant", "content": tc("rcon.execute", {"command": f"give {p} minecraft:leather_helmet 1"})},
{"role": "tool", "content": tr({"success": True, "result": "Gave 1 [Leather Cap]"})},
{"role": "assistant", "content": tc("training.save", {"lesson": "Paper RCON rejects dyed_color component syntax in give command. Must give plain leather armor — dyeing requires a crafting table with dyes.", "category": "syntax_discovery", "input_summary": "give colored leather helmet", "correct_output": "give @p minecraft:leather_helmet 1", "wrong_output": "give @p minecraft:leather_helmet[dyed_color=16711680] 1", "confidence": 0.9})},
{"role": "tool", "content": tr({"ok": True, "saved": True, "id": "lesson-042"})},
{"role": "assistant", "content": final([f"give {p} minecraft:leather_helmet 1"], None, "Attempted colored leather helmet via component syntax — Paper RCON rejected it. Gave plain helmet instead. Saved lesson about RCON component limitations.")},
]}
def training_save_error_correction():
p = random.choice(PLAYERS)
return {"messages": [
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": f"Player {p}: sudo set up a repeating command block"},
{"role": "assistant", "content": tc("rcon.execute", {"command": f"give {p} minecraft:repeating_command_block 1"})},
{"role": "tool", "content": tr({"success": False, "result": "Unknown item: minecraft:repeating_command_block"})},
{"role": "assistant", "content": tc("minecraft.lookup", {"query": "repeating command block item id 1.21"})},
{"role": "tool", "content": tr({"ok": True, "results": [{"title": "Command Block", "text": "In 1.21: minecraft:command_block (impulse), minecraft:chain_command_block, minecraft:repeating_command_block. Note: give may not work for all variants on Paper."}]})},
{"role": "assistant", "content": tc("rcon.execute", {"command": f"give {p} minecraft:command_block 1"})},
{"role": "tool", "content": tr({"success": True, "result": "Gave 1 [Command Block]"})},
{"role": "assistant", "content": tc("training.save", {"lesson": "Paper RCON may reject 'give minecraft:repeating_command_block' — use 'give minecraft:command_block' and let the player change the type in-game by right-clicking.", "category": "error_correction", "input_summary": "give repeating command block", "correct_output": "give @p minecraft:command_block 1", "wrong_output": "give @p minecraft:repeating_command_block 1", "confidence": 0.8})},
{"role": "tool", "content": tr({"ok": True, "saved": True, "id": "lesson-043"})},
{"role": "assistant", "content": final([f"give {p} minecraft:command_block 1"], None, "Repeating command block give failed. Fell back to regular command block. Player can change type in-game. Saved error correction lesson.")},
]}
def training_save_redstone_rule():
p = "RedstoneWiz"
return {"messages": [
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": f"Player {p}: sudo my repeater isn't passing signal, what's wrong?"},
{"role": "assistant", "content": tc("world.player_info", {"player": p})},
{"role": "tool", "content": tr({"ok": True, "player": p, "position": {"x": 15, "y": 64, "z": 25}, "gamemode": "creative"})},
{"role": "assistant", "content": tc("world.redstone_trace", {"start": {"x": 15, "y": 64, "z": 25}})},
{"role": "tool", "content": tr({"ok": True, "trace": [{"pos": [13, 64, 25], "type": "lever", "powered": True}, {"pos": [14, 64, 25], "type": "wire", "power": 14}, {"pos": [15, 64, 25], "type": "repeater", "facing": "west", "powered": False}], "break_point": {"pos": [15, 64, 25], "reason": "repeater facing wrong direction — input is from east but repeater faces west (output goes west, expects input from east side but signal arrives on wrong side)"}})},
{"role": "assistant", "content": tc("rcon.execute", {"command": f'tellraw {p} {{"text":"Found it! Your repeater at (15,64,25) faces west but signal comes from the east side. The repeater input/output are swapped. Break and re-place it facing east.","color":"yellow"}}'})},
{"role": "tool", "content": tr({"success": True, "result": ""})},
{"role": "assistant", "content": tc("training.save", {"lesson": "Repeater facing=west means it outputs west and expects input from east. If signal comes from west side, the repeater won't fire — it needs to be rotated.", "category": "redstone_rule", "input_summary": "repeater not passing signal", "correct_output": "Repeater must face the direction signal should travel (output direction)", "confidence": 0.95})},
{"role": "tool", "content": tr({"ok": True, "saved": True, "id": "lesson-044"})},
{"role": "assistant", "content": final([], None, "Traced redstone circuit and found repeater facing wrong direction. Explained fix via tellraw. Saved redstone rule about repeater facing direction.")},
]}
# ── Additional log.query + user.ask combos ──────────────────────────
def log_query_death_investigation():
p = random.choice(PLAYERS)
return {"messages": [
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": f"Player {p}: pray why do I keep dying?"},
{"role": "assistant", "content": tc("log.query", {"type": "deaths", "player": p, "limit": 5})},
{"role": "tool", "content": tr({"ok": True, "results": [{"type": "death", "player": p, "detail": "was slain by Zombie", "age": "2m ago"}, {"type": "death", "player": p, "detail": "was slain by Zombie", "age": "8m ago"}, {"type": "death", "player": p, "detail": "fell from a high place", "age": "15m ago"}, {"type": "death", "player": p, "detail": "was slain by Skeleton", "age": "20m ago"}], "count": 4})},
{"role": "assistant", "content": tc("journal.read", {"player": p})},
{"role": "tool", "content": tr({"ok": True, "journal": "Newer player, tends to explore caves without armor. Dies frequently to mobs."})},
{"role": "assistant", "content": final(
[f"give {p} minecraft:iron_chestplate 1", f"give {p} minecraft:iron_sword 1", f"effect give {p} minecraft:regeneration 120 1"],
"I have peered into the records of your mortal misfortunes. FOUR deaths in twenty minutes! Twice by zombie, once by skeleton, and a tragic fall. The pattern is clear: you venture forth without proper protection. Here — armor and a blade. Stop dying.",
"Queried death logs: 4 recent deaths, mostly mob-related. Journal confirms player is under-equipped. Providing armor, weapon, and regen.")},
]}
def log_query_who_was_here():
p = random.choice(PLAYERS)
return {"messages": [
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": f"Player {p}: sudo who was online earlier today?"},
{"role": "assistant", "content": tc("log.query", {"type": "joins", "limit": 10})},
{"role": "tool", "content": tr({"ok": True, "results": [
{"type": "join", "player": "SwiftWolf", "detail": "joined the game", "age": "15m ago"},
{"type": "join", "player": "DarkWolf", "detail": "left the game", "age": "30m ago"},
{"type": "join", "player": "DarkWolf", "detail": "joined the game", "age": "2h ago"},
{"type": "join", "player": "BraveWolf", "detail": "joined the game", "age": "3h ago"},
{"type": "join", "player": "BraveWolf", "detail": "left the game", "age": "1h ago"},
], "count": 5})},
{"role": "assistant", "content": final([], None, "Join/leave log shows: SwiftWolf joined 15m ago (still on), DarkWolf was on for ~1.5h (left 30m ago), BraveWolf played for 2h (left 1h ago).")},
]}
def main():
random.seed(42)
generators = [
# world.render (3 scenarios × 2 variations)
("world.render: build inspection", render_build_inspection, 2),
("world.render: cross section", render_cross_section, 2),
("world.render: redstone layout", render_redstone_layout, 2),
# server.config (4 scenarios × 2 variations)
("server.config: read gamerules", config_read_gamerules, 2),
("server.config: set keepInventory", config_set_keepinventory, 2),
("server.config: difficulty change", config_difficulty, 2),
("server.config: worldborder", config_worldborder, 2),
# user.ask (3 scenarios × 2 variations)
("user.ask: ambiguous kill", user_ask_ambiguous_kill, 2),
("user.ask: destructive fill", user_ask_destructive_fill, 2),
("user.ask: ban request", user_ask_ban_request, 2),
# training.save (3 scenarios × 2 variations)
("training.save: syntax discovery", training_save_syntax_discovery, 2),
("training.save: error correction", training_save_error_correction, 2),
("training.save: redstone rule", training_save_redstone_rule, 2),
# log.query combos (2 scenarios × 2 variations)
("log.query: death investigation", log_query_death_investigation, 2),
("log.query: who was here", log_query_who_was_here, 2),
]
all_examples = []
for name, gen_fn, count in generators:
for _ in range(count):
all_examples.append(gen_fn())
print(f" {name}: {count}")
random.shuffle(all_examples)
with open(OUTPUT, "w") as f:
for ex in all_examples:
f.write(json.dumps(ex, ensure_ascii=False) + "\n")
print(f"\nTotal: {len(all_examples)} examples written to {OUTPUT}")
if __name__ == "__main__":
main()