#!/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: {"name": "tool_name", "arguments": {...}} 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'\n{json.dumps({"name": name, "arguments": args})}\n' 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()