Files
Mortdecai/agent/tools/tool_schemas.py
T
Mortdecai f5118505b1 0.5.0 bake-off results, knowledge lookup tools, training progress chart
Bake-off (0.5.0 vs 0.4.0):
- Overall: 46.8% vs 45.2% (+1.6%), 0 errors vs 2
- Enchantments: +47% (20% → 67%)
- EssentialsX: +60% (0% → 60%)
- Effects: +25% (0% → 25%)
- Regressions: fill_build -67%, world -20%

Knowledge Lookup Tools (4 new):
- plugin.docs_lookup: WorldGuard, WorldEdit, CoreProtect, EssentialsX, LuckPerms docs
- minecraft.changelog_lookup: version history from Minecraft Wiki
- paper.docs_lookup: Paper server-specific documentation
- Wired into gateway model-driven tool loop and exploration self-play

Exploration Self-Play:
- General (vanilla MC) and plugins focus modes
- Wiki-grounded: model researches before acting, validates through RCON
- 2,243 exploration examples generated, 150 kept after quality filtering

Training Progress Chart:
- SVG chart showing training examples and inverse loss across versions
- Added to MODEL_CARD.md for Gitea display

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 15:28:09 -04:00

831 lines
32 KiB
Python

"""
Tool schemas for the Minecraft AI God model.
Defines all tools the model can call during inference, plus common RCON error
patterns used for generating error-correction training data.
Exports:
TOOL_SCHEMAS — Python list of dicts (name, description, parameters in JSON Schema)
QWEN3_TOOLS — Same tools formatted for Qwen3 chat template
RCON_ERROR_PATTERNS — Dict mapping RCON error substrings to correction metadata
"""
from typing import List, Dict, Any
# ---------------------------------------------------------------------------
# Tool definitions (canonical source of truth)
# ---------------------------------------------------------------------------
TOOL_SCHEMAS: List[Dict[str, Any]] = [
{
"name": "rcon.execute",
"description": (
"Execute a Minecraft command via RCON on the server. "
"Returns whether the command succeeded and the server's response text. "
"Commands should NOT start with a leading slash."
),
"parameters": {
"type": "object",
"properties": {
"command": {
"type": "string",
"description": "The Minecraft command to execute (no leading slash)."
}
},
"required": ["command"],
"additionalProperties": False
},
"returns": {
"type": "object",
"properties": {
"success": {"type": "boolean"},
"result": {"type": "string"}
}
}
},
{
"name": "minecraft.wiki_lookup",
"description": (
"Look up command syntax, item info, or game mechanics from the "
"Minecraft Wiki. Use this when you are unsure about exact syntax, "
"item IDs, enchantment names, effect names, or entity types."
),
"parameters": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "The search query (e.g. 'give command syntax', 'trident enchantments')."
}
},
"required": ["query"],
"additionalProperties": False
},
"returns": {
"type": "object",
"properties": {
"content": {"type": "string"},
"url": {"type": "string"}
}
}
},
{
"name": "plugin.docs_lookup",
"description": (
"Look up plugin command syntax and documentation for server plugins: "
"WorldGuard, WorldEdit/FAWE, CoreProtect, EssentialsX, LuckPerms. "
"Use this when unsure about plugin-specific command syntax, flags, "
"parameters, or configuration options."
),
"parameters": {
"type": "object",
"properties": {
"plugin": {
"type": "string",
"enum": ["worldguard", "worldedit", "coreprotect", "essentialsx", "luckperms"],
"description": "Which plugin to look up docs for."
},
"query": {
"type": "string",
"description": "What to search for (e.g. 'region flags', 'rollback syntax', 'group inheritance')."
}
},
"required": ["plugin", "query"],
"additionalProperties": False
},
"returns": {
"type": "object",
"properties": {
"content": {"type": "string"},
"url": {"type": "string"}
}
}
},
{
"name": "minecraft.changelog_lookup",
"description": (
"Look up what changed in a specific Minecraft version. Use this to check "
"if a feature, item, or command exists in the current version (1.21), "
"when something was added or removed, or what changed between versions."
),
"parameters": {
"type": "object",
"properties": {
"version": {
"type": "string",
"description": "Version to look up (e.g. '1.21', '1.20.4', '1.19'). Omit for latest."
},
"query": {
"type": "string",
"description": "What to search for in the changelog (e.g. 'mace', 'copper', 'trial chambers')."
}
},
"required": ["query"],
"additionalProperties": False
},
"returns": {
"type": "object",
"properties": {
"content": {"type": "string"},
"version": {"type": "string"},
"url": {"type": "string"}
}
}
},
{
"name": "paper.docs_lookup",
"description": (
"Look up Paper server-specific documentation — API differences from Spigot, "
"Paper-specific configuration, async chunk loading, and server optimization. "
"Use when behavior differs from vanilla or Spigot."
),
"parameters": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "What to search for (e.g. 'async chunks', 'paper-world config', 'timings')."
}
},
"required": ["query"],
"additionalProperties": False
},
"returns": {
"type": "object",
"properties": {
"content": {"type": "string"},
"url": {"type": "string"}
}
}
},
{
"name": "world.player_info",
"description": (
"Get a player's current state including health, position, and a "
"summary of their inventory. Requires the player to be online."
),
"parameters": {
"type": "object",
"properties": {
"player": {
"type": "string",
"description": "The player's in-game name (case-sensitive)."
}
},
"required": ["player"],
"additionalProperties": False
},
"returns": {
"type": "object",
"properties": {
"health": {"type": "number"},
"position": {
"type": "object",
"properties": {
"x": {"type": "integer"},
"y": {"type": "integer"},
"z": {"type": "integer"}
}
},
"inventory_summary": {"type": "string"}
}
}
},
{
"name": "world.server_state",
"description": (
"Get the current server state: time of day, weather, online players, "
"and world border size. No parameters required."
),
"parameters": {
"type": "object",
"properties": {},
"required": [],
"additionalProperties": False
},
"returns": {
"type": "object",
"properties": {
"time_of_day": {"type": "string"},
"weather": {"type": "string"},
"online_players": {
"type": "array",
"items": {"type": "string"}
},
"world_border": {"type": "number"}
}
}
},
{
"name": "world.nearby_entities",
"description": (
"Scan for entities near a player within a given radius. "
"Returns a list of entity types, counts, and distances. "
"Use this before kill/target commands to know what's around the player."
),
"parameters": {
"type": "object",
"properties": {
"player": {
"type": "string",
"description": "The player to scan around (case-sensitive)."
},
"radius": {
"type": "integer",
"description": "Search radius in blocks (default 32, max 128)."
}
},
"required": ["player"],
"additionalProperties": False
},
"returns": {
"type": "object",
"properties": {
"entities": {
"type": "array",
"items": {
"type": "object",
"properties": {
"type": {"type": "string"},
"count": {"type": "integer"},
"nearest_distance": {"type": "number"}
}
}
},
"total": {"type": "integer"}
}
}
},
{
"name": "memory.read",
"description": (
"Read saved memories for a player — locations, preferences, and facts. "
"Use this when a player references a saved location (e.g. 'tp me home') "
"or asks what you know about them."
),
"parameters": {
"type": "object",
"properties": {
"player": {
"type": "string",
"description": "Player whose memories to read (omit for requesting player)."
},
"key": {
"type": "string",
"description": "Specific memory key to look up (e.g. 'home', 'base'). Omit to get all."
}
},
"additionalProperties": False
},
"returns": {
"type": "object",
"properties": {
"memories": {
"type": "array",
"items": {
"type": "object",
"properties": {
"key": {"type": "string"},
"type": {"type": "string"},
"value": {}
}
}
}
}
}
},
{
"name": "memory.write",
"description": (
"Save a memory for a player — a named location, preference, or fact. "
"Use this when a player asks you to remember something."
),
"parameters": {
"type": "object",
"properties": {
"player": {
"type": "string",
"description": "Player who owns this memory."
},
"type": {
"type": "string",
"enum": ["location", "preference", "fact"],
"description": "Memory type."
},
"key": {
"type": "string",
"description": "Memory name (e.g. 'home', 'base', 'favorite_tool')."
},
"value": {
"description": "Memory data — {x,y,z} for locations, string for others."
}
},
"required": ["player", "type", "key", "value"],
"additionalProperties": False
},
"returns": {
"type": "object",
"properties": {
"ok": {"type": "boolean"},
"key": {"type": "string"}
}
}
},
# ── Script environment tools ──────────────────────────────────────────
{
"name": "script.write",
"description": (
"Write a mcfunction script to the Mortdecai datapack. Each line is one "
"Minecraft command (no leading slash). Use relative coordinates (~) for "
"position-relative builds. Scripts are stored at "
"mortdecai:functions/<name>.mcfunction. Overwrites if the script already exists."
),
"parameters": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Script name (e.g. 'build_house', 'arena_setup'). Lowercase, underscores."
},
"commands": {
"type": "array",
"items": {"type": "string"},
"description": "List of Minecraft commands, one per line. No leading slash."
},
"description": {
"type": "string",
"description": "Comment at the top of the file describing what this script does."
}
},
"required": ["name", "commands"],
"additionalProperties": False
},
"returns": {
"type": "object",
"properties": {
"ok": {"type": "boolean"},
"path": {"type": "string"},
"lines": {"type": "integer"}
}
}
},
{
"name": "script.validate",
"description": (
"Validate a list of commands without writing or executing. Dry-runs each "
"command through RCON and reports errors. Use this before script.write to "
"catch mistakes. Returns per-line pass/fail results."
),
"parameters": {
"type": "object",
"properties": {
"commands": {
"type": "array",
"items": {"type": "string"},
"description": "List of Minecraft commands to validate."
}
},
"required": ["commands"],
"additionalProperties": False
},
"returns": {
"type": "object",
"properties": {
"valid": {"type": "boolean"},
"total": {"type": "integer"},
"passed": {"type": "integer"},
"errors": {
"type": "array",
"items": {
"type": "object",
"properties": {
"line": {"type": "integer"},
"command": {"type": "string"},
"error": {"type": "string"}
}
}
}
}
}
},
{
"name": "script.execute",
"description": (
"Execute a previously written mcfunction script. Optionally run it as/at "
"a specific player for correct relative coordinates."
),
"parameters": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Script name to execute (e.g. 'build_house')."
},
"as_player": {
"type": "string",
"description": "Run the script as this player (for ~ coordinates). Optional."
}
},
"required": ["name"],
"additionalProperties": False
},
"returns": {
"type": "object",
"properties": {
"ok": {"type": "boolean"},
"result": {"type": "string"}
}
}
},
{
"name": "script.read",
"description": (
"Read the contents of an existing mcfunction script. "
"Use this to review or debug a script before modifying it."
),
"parameters": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Script name to read."
}
},
"required": ["name"],
"additionalProperties": False
},
"returns": {
"type": "object",
"properties": {
"ok": {"type": "boolean"},
"commands": {
"type": "array",
"items": {"type": "string"}
},
"lines": {"type": "integer"}
}
}
},
{
"name": "script.list",
"description": (
"List all scripts in the Mortdecai datapack. Returns script names, "
"line counts, and whether they are scheduled (tick/load)."
),
"parameters": {
"type": "object",
"properties": {},
"additionalProperties": False
},
"returns": {
"type": "object",
"properties": {
"scripts": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {"type": "string"},
"lines": {"type": "integer"},
"scheduled": {"type": "string", "description": "tick, load, or none"}
}
}
}
}
}
},
{
"name": "script.delete",
"description": (
"Delete a mcfunction script from the datapack. Also removes it from "
"tick/load schedules if applicable."
),
"parameters": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Script name to delete."
}
},
"required": ["name"],
"additionalProperties": False
},
"returns": {
"type": "object",
"properties": {
"ok": {"type": "boolean"}
}
}
},
{
"name": "script.schedule",
"description": (
"Schedule a script to run automatically on tick (every game tick = 50ms) "
"or on load (when server starts/reloads). Use tick for continuous effects, "
"load for initialization. WARNING: tick functions run 20x/second — keep them "
"lightweight or they will lag the server."
),
"parameters": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Script name to schedule."
},
"type": {
"type": "string",
"enum": ["tick", "load"],
"description": "Schedule type."
}
},
"required": ["name", "type"],
"additionalProperties": False
},
"returns": {
"type": "object",
"properties": {
"ok": {"type": "boolean"}
}
}
},
]
# ---------------------------------------------------------------------------
# Qwen3 tool format (for chat template injection)
# ---------------------------------------------------------------------------
def _to_qwen3_tool(schema: Dict[str, Any]) -> Dict[str, Any]:
"""Convert a tool schema dict to the Qwen3 function-calling format."""
return {
"type": "function",
"function": {
"name": schema["name"],
"description": schema["description"],
"parameters": schema["parameters"],
}
}
QWEN3_TOOLS: List[Dict[str, Any]] = [_to_qwen3_tool(t) for t in TOOL_SCHEMAS]
def qwen3_tools_block() -> str:
"""Return the tools block string to inject into the system prompt for Qwen3."""
import json
lines = ["# Tools", "", "You may call one or more functions to assist.",
"The available tools are:", ""]
for tool in QWEN3_TOOLS:
lines.append(json.dumps(tool, indent=2))
lines.append("")
return "\n".join(lines)
# ---------------------------------------------------------------------------
# Common RCON error patterns and their corrections
# ---------------------------------------------------------------------------
RCON_ERROR_PATTERNS: Dict[str, Dict[str, Any]] = {
# --- Syntax / structural errors ---
"Unknown or incomplete command": {
"type": "syntax_error",
"description": "Command is malformed, misspelled, or missing subcommands.",
"common_causes": [
"Missing 'give' subcommand in effect (e.g. 'effect player' instead of 'effect give player')",
"Invalid weather value (e.g. 'weather storm' instead of 'weather thunder')",
"Old NBT syntax that the parser cannot parse",
"Missing minecraft: namespace prefix",
],
"example_wrong": "effect slingshooter08 minecraft:strength 300 2",
"example_right": "effect give slingshooter08 minecraft:strength 300 2",
},
"Incorrect argument for command": {
"type": "wrong_argument",
"description": "A command argument has an invalid type or value.",
"common_causes": [
"Wrong gamemode string (e.g. 's' instead of 'survival')",
"Non-integer where integer expected",
"Invalid enchantment or effect name",
],
"example_wrong": "gamemode s slingshooter08",
"example_right": "gamemode survival slingshooter08",
},
"Expected whitespace": {
"type": "missing_space",
"description": "Parser expected a space between tokens but found something else.",
"common_causes": [
"Missing space before count in give command",
"Squashed arguments with no separator",
],
"example_wrong": "give slingshooter08 minecraft:diamond_sword1",
"example_right": "give slingshooter08 minecraft:diamond_sword 1",
},
"Unknown item 'minecraft:": {
"type": "invalid_item",
"description": "The item ID does not exist in 1.21.",
"common_causes": [
"Using generic name (bed -> white_bed, log -> oak_log, wood -> oak_planks)",
"Misspelled item ID",
"Using a removed or renamed item",
],
"corrections": {
"minecraft:bed": "minecraft:white_bed",
"minecraft:log": "minecraft:oak_log",
"minecraft:wood": "minecraft:oak_planks",
"minecraft:plank": "minecraft:oak_planks",
"minecraft:stone_brick": "minecraft:stone_bricks",
"minecraft:wooden_sword": "minecraft:wooden_sword",
"minecraft:iron": "minecraft:iron_ingot",
"minecraft:gold": "minecraft:gold_ingot",
"minecraft:diamond": "minecraft:diamond",
"minecraft:notch_apple": "minecraft:enchanted_golden_apple",
"minecraft:gapple": "minecraft:golden_apple",
"minecraft:grass": "minecraft:short_grass",
"minecraft:boat": "minecraft:oak_boat",
},
"example_wrong": "give slingshooter08 minecraft:bed 1",
"example_right": "give slingshooter08 minecraft:white_bed 1",
},
"No player was found": {
"type": "player_not_found",
"description": "The specified player is not online or the name is wrong.",
"common_causes": [
"Player logged off",
"Case-sensitive name mismatch",
"Typo in player name",
],
"example_wrong": "give Slingshooter08 minecraft:diamond 1",
"example_right": "give slingshooter08 minecraft:diamond 1",
},
"That position is not loaded": {
"type": "unloaded_chunk",
"description": "The target coordinates are in an unloaded chunk.",
"common_causes": [
"Filling/placing blocks far from any player",
"Teleporting to unloaded area without a player there",
],
"example_wrong": "setblock 50000 64 50000 minecraft:diamond_block",
"example_right": "execute at slingshooter08 run setblock ~ ~1 ~ minecraft:diamond_block",
},
"Invalid or unknown enchantment": {
"type": "invalid_enchantment",
"description": "The enchantment name is wrong or incompatible with the item.",
"common_causes": [
"Typo in enchantment name",
"Using an enchantment on an incompatible item (e.g. sharpness on a bow)",
"Old enchantment names",
],
"example_wrong": "give slingshooter08 minecraft:bow[enchantments={sharpness:5}] 1",
"example_right": "give slingshooter08 minecraft:bow[enchantments={power:5}] 1",
},
"Could not parse the remainder": {
"type": "nbt_parse_error",
"description": "The NBT/component data could not be parsed.",
"common_causes": [
"Old {Enchantments:[{id:...,lvl:...}]} NBT syntax instead of 1.21 component syntax",
"Mismatched brackets or braces",
"Extra trailing data",
],
"example_wrong": "give slingshooter08 minecraft:diamond_sword{Enchantments:[{id:sharpness,lvl:5}]} 1",
"example_right": "give slingshooter08 minecraft:diamond_sword[enchantments={sharpness:5}] 1",
},
"Unknown effect 'minecraft:": {
"type": "invalid_effect",
"description": "The effect ID does not exist.",
"common_causes": [
"Misspelled effect name",
"Using display name instead of ID (e.g. 'haste' is correct, not 'fast_digging')",
],
"corrections": {
"minecraft:fast_digging": "minecraft:haste",
"minecraft:slow_digging": "minecraft:mining_fatigue",
"minecraft:confusion": "minecraft:nausea",
"minecraft:damage_boost": "minecraft:strength",
"minecraft:harm": "minecraft:instant_damage",
"minecraft:heal": "minecraft:instant_health",
},
"example_wrong": "effect give slingshooter08 minecraft:fast_digging 300 2",
"example_right": "effect give slingshooter08 minecraft:haste 300 2",
},
"Unknown entity type": {
"type": "invalid_entity",
"description": "The entity type does not exist in 1.21.",
"common_causes": [
"Missing minecraft: prefix",
"Old entity name (e.g. 'PigZombie' -> 'zombified_piglin')",
],
"corrections": {
"minecraft:pigzombie": "minecraft:zombified_piglin",
"minecraft:pig_zombie": "minecraft:zombified_piglin",
"minecraft:zombie_pigman": "minecraft:zombified_piglin",
},
"example_wrong": "summon minecraft:pig_zombie ~ ~ ~",
"example_right": "summon minecraft:zombified_piglin ~ ~ ~",
},
}
# ---------------------------------------------------------------------------
# Specific error scenarios for training data generation
# ---------------------------------------------------------------------------
ERROR_SCENARIOS: List[Dict[str, Any]] = [
{
"id": "missing_prefix",
"description": "Missing minecraft: prefix on item ID",
"wrong_command": "give {player} diamond_sword 1",
"error_message": "Unknown or incomplete command, see below for error at position ...",
"correct_command": "give {player} minecraft:diamond_sword 1",
"reasoning": "Item IDs require the minecraft: namespace prefix in 1.21+.",
},
{
"id": "old_nbt_enchantments",
"description": "Old NBT enchantment syntax instead of 1.21 component syntax",
"wrong_command": 'give {player} minecraft:diamond_sword{{Enchantments:[{{id:"minecraft:sharpness",lvl:5}}]}} 1',
"error_message": "Could not parse the remainder of the data tag",
"correct_command": "give {player} minecraft:diamond_sword[enchantments={{sharpness:5,unbreaking:3,fire_aspect:2}}] 1",
"reasoning": "1.21 uses component syntax item[enchantments={name:level}] not old NBT {Enchantments:[...]}.",
},
{
"id": "invalid_effect_name",
"description": "Invalid or old effect name",
"wrong_command": "effect give {player} minecraft:fast_digging 300 2",
"error_message": "Unknown effect 'minecraft:fast_digging'",
"correct_command": "effect give {player} minecraft:haste 300 2",
"reasoning": "The effect is called 'haste' in 1.21, not 'fast_digging'.",
},
{
"id": "wrong_item_bed",
"description": "Generic 'bed' instead of color-specific bed",
"wrong_command": "give {player} minecraft:bed 1",
"error_message": "Unknown item 'minecraft:bed'",
"correct_command": "give {player} minecraft:white_bed 1",
"reasoning": "In 1.13+, beds require a color prefix. 'bed' is not a valid item; use 'white_bed', 'red_bed', etc.",
},
{
"id": "wrong_item_log",
"description": "Generic 'log' instead of wood-specific log",
"wrong_command": "give {player} minecraft:log 64",
"error_message": "Unknown item 'minecraft:log'",
"correct_command": "give {player} minecraft:oak_log 64",
"reasoning": "In 1.13+, logs require a wood type prefix. Use 'oak_log', 'birch_log', etc.",
},
{
"id": "count_wrong_position",
"description": "Count placed before item in give command",
"wrong_command": "give {player} 64 minecraft:diamond",
"error_message": "Incorrect argument for command at position ...: Expected item, got '64'",
"correct_command": "give {player} minecraft:diamond 64",
"reasoning": "Give command syntax is: give <player> <item> [count]. Count comes after item, not before.",
},
{
"id": "effect_missing_give",
"description": "Missing 'give' subcommand in effect command",
"wrong_command": "effect {player} minecraft:speed 300 2",
"error_message": "Unknown or incomplete command, see below for error at position ...",
"correct_command": "effect give {player} minecraft:speed 300 2",
"reasoning": "In 1.21, effect requires a subcommand: 'effect give', 'effect clear'. Bare 'effect <player>' is invalid.",
},
{
"id": "weather_storm",
"description": "Invalid weather value 'storm'",
"wrong_command": "weather storm",
"error_message": "Unknown or incomplete command, see below for error at position ...",
"correct_command": "weather thunder",
"reasoning": "Valid weather values are: clear, rain, thunder. 'storm' is not valid; use 'thunder'.",
},
{
"id": "gamemode_abbreviation",
"description": "Gamemode abbreviation instead of full name",
"wrong_command": "gamemode c {player}",
"error_message": "Incorrect argument for command at position ...: Invalid game mode 'c'",
"correct_command": "gamemode creative {player}",
"reasoning": "Gamemode requires full names in 1.21: survival, creative, adventure, spectator. Abbreviations are invalid.",
},
{
"id": "wrong_item_grass",
"description": "Old 'grass' item renamed to 'short_grass'",
"wrong_command": "give {player} minecraft:grass 64",
"error_message": "Unknown item 'minecraft:grass'",
"correct_command": "give {player} minecraft:short_grass 64",
"reasoning": "In 1.20.3+, 'grass' was renamed to 'short_grass'. The block 'grass_block' is separate.",
},
{
"id": "summon_no_prefix",
"description": "Summon without minecraft: prefix",
"wrong_command": "summon zombie ~ ~ ~",
"error_message": "Unknown entity type: zombie",
"correct_command": "summon minecraft:zombie ~ ~ ~",
"reasoning": "Entity types require the minecraft: namespace prefix.",
},
{
"id": "old_zombie_pigman",
"description": "Old zombie pigman name",
"wrong_command": "summon minecraft:zombie_pigman ~ ~ ~",
"error_message": "Unknown entity type: minecraft:zombie_pigman",
"correct_command": "summon minecraft:zombified_piglin ~ ~ ~",
"reasoning": "Zombie pigmen were renamed to zombified piglins in 1.16.",
},
]