diff --git a/src/cli/service/gemini_ai_service.cc b/src/cli/service/gemini_ai_service.cc index bc52f737..c06cbc62 100644 --- a/src/cli/service/gemini_ai_service.cc +++ b/src/cli/service/gemini_ai_service.cc @@ -19,30 +19,23 @@ namespace cli { GeminiAIService::GeminiAIService(const GeminiConfig& config) : config_(config) { + // Load command documentation into prompt builder + prompt_builder_.LoadResourceCatalogue(""); // TODO: Pass actual yaml path when available + if (config_.system_instruction.empty()) { - config_.system_instruction = BuildSystemInstruction(); + // Use enhanced prompting by default + if (config_.use_enhanced_prompting) { + config_.system_instruction = prompt_builder_.BuildSystemInstructionWithExamples(); + } else { + config_.system_instruction = BuildSystemInstruction(); + } } } std::string GeminiAIService::BuildSystemInstruction() { - return R"(You are an expert ROM hacking assistant for The Legend of Zelda: A Link to the Past. - -Your task is to generate a sequence of z3ed CLI commands to achieve the user's request. - -CRITICAL: Respond ONLY with a JSON array of strings. Each string must be a complete z3ed command. - -Available z3ed commands: -- palette export --group --id --to -- palette import --group --id --from -- palette set-color --file --index --color -- overworld set-tile --map --x --y --tile -- sprite set-position --id --x --y -- dungeon set-room-tile --room --x --y --tile - -Example response format: -["z3ed palette export --group overworld --id 0 --to palette.json", "z3ed palette set-color --file palette.json --index 5 --color 0xFF0000"] - -Do not include explanations, markdown formatting, or code blocks. Only the JSON array.)"; + // Fallback prompt if enhanced prompting is disabled + // Use PromptBuilder's basic system instruction + return prompt_builder_.BuildSystemInstruction(); } absl::Status GeminiAIService::CheckAvailability() { diff --git a/src/cli/service/gemini_ai_service.h b/src/cli/service/gemini_ai_service.h index 56f7a67c..b79d8985 100644 --- a/src/cli/service/gemini_ai_service.h +++ b/src/cli/service/gemini_ai_service.h @@ -7,6 +7,7 @@ #include "absl/status/status.h" #include "absl/status/statusor.h" #include "cli/service/ai_service.h" +#include "cli/service/prompt_builder.h" namespace yaze { namespace cli { @@ -17,6 +18,7 @@ struct GeminiConfig { float temperature = 0.7f; int max_output_tokens = 2048; std::string system_instruction; + bool use_enhanced_prompting = true; // Enable few-shot examples GeminiConfig() = default; explicit GeminiConfig(const std::string& key) : api_key(key) {} @@ -39,6 +41,7 @@ class GeminiAIService : public AIService { const std::string& response_body); GeminiConfig config_; + PromptBuilder prompt_builder_; }; } // namespace cli diff --git a/src/cli/service/ollama_ai_service.cc b/src/cli/service/ollama_ai_service.cc index 50cc145a..28db2e49 100644 --- a/src/cli/service/ollama_ai_service.cc +++ b/src/cli/service/ollama_ai_service.cc @@ -31,52 +31,23 @@ namespace yaze { namespace cli { OllamaAIService::OllamaAIService(const OllamaConfig& config) : config_(config) { + // Load command documentation into prompt builder + prompt_builder_.LoadResourceCatalogue(""); // TODO: Pass actual yaml path when available + if (config_.system_prompt.empty()) { - config_.system_prompt = BuildSystemPrompt(); + // Use enhanced prompting by default + if (config_.use_enhanced_prompting) { + config_.system_prompt = prompt_builder_.BuildSystemInstructionWithExamples(); + } else { + config_.system_prompt = BuildSystemPrompt(); + } } } std::string OllamaAIService::BuildSystemPrompt() { - // TODO: Eventually load from docs/api/z3ed-resources.yaml for full command catalogue - // For now, use a comprehensive hardcoded prompt - return R"(You are an expert ROM hacking assistant for The Legend of Zelda: A Link to the Past. -Your role is to generate PRECISE z3ed CLI commands to fulfill user requests. - -CRITICAL RULES: -1. Output ONLY a JSON array of command strings -2. Each command must follow exact z3ed syntax -3. Commands must be executable without modification -4. Use only commands from the available command set -5. Include all required arguments with proper flags - -AVAILABLE COMMANDS: -- rom info --rom -- rom validate --rom -- rom diff --rom1 --rom2 -- palette export --group --id --to -- palette import --group --id --from -- palette set-color --file --index --color -- overworld get-tile --map --x --y -- overworld set-tile --map --x --y --tile -- dungeon export-room --room --to -- dungeon import-room --room --from - -RESPONSE FORMAT: -["command1", "command2", "command3"] - -EXAMPLE 1: -User: "Validate the ROM" -Response: ["rom validate --rom zelda3.sfc"] - -EXAMPLE 2: -User: "Make all soldier armors red" -Response: ["palette export --group sprites --id soldier --to /tmp/soldier.pal", "palette set-color --file /tmp/soldier.pal --index 5 --color FF0000", "palette import --group sprites --id soldier --from /tmp/soldier.pal"] - -EXAMPLE 3: -User: "Export the first overworld palette" -Response: ["palette export --group overworld --id 0 --to /tmp/ow_pal_0.pal"] - -Begin your response now.)"; + // Fallback prompt if enhanced prompting is disabled + // Use PromptBuilder's basic system instruction + return prompt_builder_.BuildSystemInstruction(); } absl::Status OllamaAIService::CheckAvailability() { diff --git a/src/cli/service/ollama_ai_service.h b/src/cli/service/ollama_ai_service.h index a0f076d7..2ed3a280 100644 --- a/src/cli/service/ollama_ai_service.h +++ b/src/cli/service/ollama_ai_service.h @@ -7,6 +7,7 @@ #include "absl/status/status.h" #include "absl/status/statusor.h" #include "cli/service/ai_service.h" +#include "cli/service/prompt_builder.h" namespace yaze { namespace cli { @@ -18,6 +19,7 @@ struct OllamaConfig { float temperature = 0.1; // Low temp for deterministic commands int max_tokens = 2048; // Sufficient for command lists std::string system_prompt; // Injected from resource catalogue + bool use_enhanced_prompting = true; // Enable few-shot examples }; class OllamaAIService : public AIService { @@ -36,6 +38,7 @@ class OllamaAIService : public AIService { private: OllamaConfig config_; + PromptBuilder prompt_builder_; // Build system prompt from resource catalogue std::string BuildSystemPrompt(); diff --git a/src/cli/service/prompt_builder.cc b/src/cli/service/prompt_builder.cc new file mode 100644 index 00000000..cc3d851a --- /dev/null +++ b/src/cli/service/prompt_builder.cc @@ -0,0 +1,300 @@ +#include "cli/service/prompt_builder.h" + +#include +#include + +#include "absl/strings/str_cat.h" +#include "absl/strings/str_join.h" + +namespace yaze { +namespace cli { + +PromptBuilder::PromptBuilder() { + LoadDefaultExamples(); +} + +void PromptBuilder::LoadDefaultExamples() { + // Palette manipulation examples + examples_.push_back({ + "Change the color at index 5 in palette 0 to red", + { + "palette export --group overworld --id 0 --to temp_palette.json", + "palette set-color --file temp_palette.json --index 5 --color 0xFF0000", + "palette import --group overworld --id 0 --from temp_palette.json" + }, + "Export palette, modify specific color, then import back" + }); + + examples_.push_back({ + "Make all soldiers red", + { + "palette export --group sprite --id 3 --to soldier_palette.json", + "palette set-color --file soldier_palette.json --index 1 --color 0xFF0000", + "palette set-color --file soldier_palette.json --index 2 --color 0xCC0000", + "palette import --group sprite --id 3 --from soldier_palette.json" + }, + "Modify multiple colors in a sprite palette" + }); + + // Overworld manipulation examples + examples_.push_back({ + "Place a tree at coordinates (10, 20) on map 0", + { + "overworld set-tile --map 0 --x 10 --y 20 --tile 0x02E" + }, + "Tree tile ID is 0x02E in ALTTP" + }); + + examples_.push_back({ + "Put a house at position 5, 5", + { + "overworld set-tile --map 0 --x 5 --y 5 --tile 0x0C0", + "overworld set-tile --map 0 --x 6 --y 5 --tile 0x0C1", + "overworld set-tile --map 0 --x 5 --y 6 --tile 0x0D0", + "overworld set-tile --map 0 --x 6 --y 6 --tile 0x0D1" + }, + "Houses require 4 tiles (2x2 grid)" + }); + + // Validation examples + examples_.push_back({ + "Validate the ROM", + { + "rom validate" + }, + "Simple validation command" + }); + + examples_.push_back({ + "Check if my changes are valid", + { + "rom validate" + }, + "Validation ensures ROM integrity" + }); +} + +absl::Status PromptBuilder::LoadResourceCatalogue(const std::string& yaml_path) { + // TODO: Parse z3ed-resources.yaml when available + // For now, use hardcoded command reference + + command_docs_["palette export"] = + "Export palette data to JSON file\n" + " --group Palette group (overworld, dungeon, sprite)\n" + " --id Palette ID (0-based index)\n" + " --to Output JSON file path"; + + command_docs_["palette import"] = + "Import palette data from JSON file\n" + " --group Palette group (overworld, dungeon, sprite)\n" + " --id Palette ID (0-based index)\n" + " --from Input JSON file path"; + + command_docs_["palette set-color"] = + "Modify a color in palette JSON file\n" + " --file Palette JSON file to modify\n" + " --index Color index (0-15 per palette)\n" + " --color New color in hex (0xRRGGBB format)"; + + command_docs_["overworld set-tile"] = + "Place a tile in the overworld\n" + " --map Map ID (0-based)\n" + " --x X coordinate (0-63)\n" + " --y Y coordinate (0-63)\n" + " --tile Tile ID in hex (e.g., 0x02E for tree)"; + + command_docs_["sprite set-position"] = + "Move a sprite to new position\n" + " --id Sprite ID\n" + " --x X coordinate\n" + " --y Y coordinate"; + + command_docs_["dungeon set-room-tile"] = + "Place a tile in dungeon room\n" + " --room Room ID\n" + " --x X coordinate\n" + " --y Y coordinate\n" + " --tile Tile ID"; + + command_docs_["rom validate"] = + "Validate ROM integrity and structure"; + + catalogue_loaded_ = true; + return absl::OkStatus(); +} + +std::string PromptBuilder::BuildCommandReference() { + std::ostringstream oss; + + oss << "# Available z3ed Commands\n\n"; + + for (const auto& [cmd, docs] : command_docs_) { + oss << "## " << cmd << "\n"; + oss << docs << "\n\n"; + } + + return oss.str(); +} + +std::string PromptBuilder::BuildFewShotExamplesSection() { + std::ostringstream oss; + + oss << "# Example Command Sequences\n\n"; + oss << "Here are proven examples of how to accomplish common tasks:\n\n"; + + for (const auto& example : examples_) { + oss << "**User Request:** \"" << example.user_prompt << "\"\n"; + oss << "**Commands:**\n"; + oss << "```json\n["; + + std::vector quoted_cmds; + for (const auto& cmd : example.expected_commands) { + quoted_cmds.push_back("\"" + cmd + "\""); + } + oss << absl::StrJoin(quoted_cmds, ", "); + + oss << "]\n```\n"; + oss << "*Explanation:* " << example.explanation << "\n\n"; + } + + return oss.str(); +} + +std::string PromptBuilder::BuildConstraintsSection() { + return R"( +# Critical Constraints + +1. **Output Format:** You MUST respond with ONLY a JSON array of strings + - Each string is a complete z3ed command + - NO explanatory text before or after + - NO markdown code blocks (```json) + - NO "z3ed" prefix in commands + +2. **Command Syntax:** Follow the exact syntax shown in examples + - Use correct flag names (--group, --id, --to, --from, etc.) + - Use hex format for colors (0xRRGGBB) and tile IDs (0xNNN) + - Coordinates are 0-based indices + +3. **Common Patterns:** + - Palette modifications: export → set-color → import + - Multiple tile placement: multiple overworld set-tile commands + - Validation: single rom validate command + +4. **Tile IDs Reference (ALTTP):** + - Tree: 0x02E + - House (2x2): 0x0C0, 0x0C1, 0x0D0, 0x0D1 + - Water: 0x038 + - Grass: 0x000 + +5. **Error Prevention:** + - Always export before modifying palettes + - Use temporary file names (temp_*.json) for intermediate files + - Validate coordinates are within bounds +)"; +} + +std::string PromptBuilder::BuildContextSection(const RomContext& context) { + std::ostringstream oss; + + oss << "# Current ROM Context\n\n"; + + if (context.rom_loaded) { + oss << "- **ROM Loaded:** Yes (" << context.rom_path << ")\n"; + } else { + oss << "- **ROM Loaded:** No\n"; + } + + if (!context.current_editor.empty()) { + oss << "- **Active Editor:** " << context.current_editor << "\n"; + } + + if (!context.editor_state.empty()) { + oss << "- **Editor State:**\n"; + for (const auto& [key, value] : context.editor_state) { + oss << " - " << key << ": " << value << "\n"; + } + } + + oss << "\n"; + return oss.str(); +} + +std::string PromptBuilder::BuildSystemInstruction() { + std::ostringstream oss; + + oss << "You are an expert ROM hacking assistant for The Legend of Zelda: " + << "A Link to the Past (ALTTP).\n\n"; + + oss << "Your task is to generate a sequence of z3ed CLI commands to achieve " + << "the user's request.\n\n"; + + if (catalogue_loaded_) { + oss << BuildCommandReference(); + } + + oss << BuildConstraintsSection(); + + oss << "\n**Response Format:**\n"; + oss << "```json\n"; + oss << "[\"command1 --flag value\", \"command2 --flag value\"]\n"; + oss << "```\n"; + + return oss.str(); +} + +std::string PromptBuilder::BuildSystemInstructionWithExamples() { + std::ostringstream oss; + + oss << BuildSystemInstruction(); + oss << "\n---\n\n"; + oss << BuildFewShotExamplesSection(); + + return oss.str(); +} + +std::string PromptBuilder::BuildContextualPrompt( + const std::string& user_prompt, + const RomContext& context) { + std::ostringstream oss; + + if (context.rom_loaded || !context.current_editor.empty()) { + oss << BuildContextSection(context); + oss << "---\n\n"; + } + + oss << "**User Request:** " << user_prompt << "\n\n"; + oss << "Generate the appropriate z3ed commands as a JSON array."; + + return oss.str(); +} + +void PromptBuilder::AddFewShotExample(const FewShotExample& example) { + examples_.push_back(example); +} + +std::vector PromptBuilder::GetExamplesForCategory( + const std::string& category) { + std::vector result; + + for (const auto& example : examples_) { + // Simple category matching based on keywords + if (category == "palette" && + (example.user_prompt.find("palette") != std::string::npos || + example.user_prompt.find("color") != std::string::npos)) { + result.push_back(example); + } else if (category == "overworld" && + (example.user_prompt.find("place") != std::string::npos || + example.user_prompt.find("tree") != std::string::npos || + example.user_prompt.find("house") != std::string::npos)) { + result.push_back(example); + } else if (category == "validation" && + example.user_prompt.find("validate") != std::string::npos) { + result.push_back(example); + } + } + + return result; +} + +} // namespace cli +} // namespace yaze diff --git a/src/cli/service/prompt_builder.h b/src/cli/service/prompt_builder.h new file mode 100644 index 00000000..6054d437 --- /dev/null +++ b/src/cli/service/prompt_builder.h @@ -0,0 +1,74 @@ +#ifndef YAZE_CLI_SERVICE_PROMPT_BUILDER_H_ +#define YAZE_CLI_SERVICE_PROMPT_BUILDER_H_ + +#include +#include +#include + +#include "absl/status/statusor.h" + +namespace yaze { +namespace cli { + +// Few-shot example for prompt engineering +struct FewShotExample { + std::string user_prompt; + std::vector expected_commands; + std::string explanation; // Why these commands work +}; + +// ROM context information to inject into prompts +struct RomContext { + std::string rom_path; + bool rom_loaded = false; + std::string current_editor; // "overworld", "dungeon", "sprite", etc. + std::map editor_state; // Context-specific state +}; + +// Builds sophisticated prompts for LLM services +class PromptBuilder { + public: + PromptBuilder(); + + // Load z3ed command documentation from resources + absl::Status LoadResourceCatalogue(const std::string& yaml_path); + + // Build system instruction with full command reference + std::string BuildSystemInstruction(); + + // Build system instruction with few-shot examples + std::string BuildSystemInstructionWithExamples(); + + // Build user prompt with ROM context + std::string BuildContextualPrompt( + const std::string& user_prompt, + const RomContext& context); + + // Add custom few-shot examples + void AddFewShotExample(const FewShotExample& example); + + // Get few-shot examples for specific category + std::vector GetExamplesForCategory( + const std::string& category); + + // Set verbosity level (0=minimal, 1=standard, 2=verbose) + void SetVerbosity(int level) { verbosity_ = level; } + + private: + std::string BuildCommandReference(); + std::string BuildFewShotExamplesSection(); + std::string BuildContextSection(const RomContext& context); + std::string BuildConstraintsSection(); + + void LoadDefaultExamples(); + + std::map command_docs_; // Command name -> docs + std::vector examples_; + int verbosity_ = 1; + bool catalogue_loaded_ = false; +}; + +} // namespace cli +} // namespace yaze + +#endif // YAZE_CLI_SERVICE_PROMPT_BUILDER_H_ diff --git a/src/cli/z3ed.cmake b/src/cli/z3ed.cmake index 446a8831..8b3506bc 100644 --- a/src/cli/z3ed.cmake +++ b/src/cli/z3ed.cmake @@ -48,6 +48,7 @@ add_executable( cli/handlers/agent/gui_commands.cc cli/service/ai_service.cc cli/service/ollama_ai_service.cc + cli/service/prompt_builder.cc cli/service/proposal_registry.cc cli/service/resource_catalog.cc cli/service/rom_sandbox_manager.cc