feat: Add PromptBuilder for enhanced command prompting and examples

This commit is contained in:
scawful
2025-10-03 01:32:18 -04:00
parent d875b45fcd
commit ead4abbf33
7 changed files with 405 additions and 60 deletions

View File

@@ -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 <group> --id <id> --to <file>
- palette import --group <group> --id <id> --from <file>
- palette set-color --file <file> --index <index> --color <hex_color>
- overworld set-tile --map <map_id> --x <x> --y <y> --tile <tile_id>
- sprite set-position --id <id> --x <x> --y <y>
- dungeon set-room-tile --room <room_id> --x <x> --y <y> --tile <tile_id>
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() {

View File

@@ -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

View File

@@ -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 <path>
- rom validate --rom <path>
- rom diff --rom1 <path1> --rom2 <path2>
- palette export --group <group> --id <id> --to <file>
- palette import --group <group> --id <id> --from <file>
- palette set-color --file <file> --index <index> --color <hex_color>
- overworld get-tile --map <map_id> --x <x> --y <y>
- overworld set-tile --map <map_id> --x <x> --y <y> --tile <tile_id>
- dungeon export-room --room <room_id> --to <file>
- dungeon import-room --room <room_id> --from <file>
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() {

View File

@@ -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();

View File

@@ -0,0 +1,300 @@
#include "cli/service/prompt_builder.h"
#include <fstream>
#include <sstream>
#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 <group> Palette group (overworld, dungeon, sprite)\n"
" --id <id> Palette ID (0-based index)\n"
" --to <file> Output JSON file path";
command_docs_["palette import"] =
"Import palette data from JSON file\n"
" --group <group> Palette group (overworld, dungeon, sprite)\n"
" --id <id> Palette ID (0-based index)\n"
" --from <file> Input JSON file path";
command_docs_["palette set-color"] =
"Modify a color in palette JSON file\n"
" --file <file> Palette JSON file to modify\n"
" --index <index> Color index (0-15 per palette)\n"
" --color <hex> New color in hex (0xRRGGBB format)";
command_docs_["overworld set-tile"] =
"Place a tile in the overworld\n"
" --map <id> Map ID (0-based)\n"
" --x <x> X coordinate (0-63)\n"
" --y <y> Y coordinate (0-63)\n"
" --tile <hex> Tile ID in hex (e.g., 0x02E for tree)";
command_docs_["sprite set-position"] =
"Move a sprite to new position\n"
" --id <id> Sprite ID\n"
" --x <x> X coordinate\n"
" --y <y> Y coordinate";
command_docs_["dungeon set-room-tile"] =
"Place a tile in dungeon room\n"
" --room <id> Room ID\n"
" --x <x> X coordinate\n"
" --y <y> Y coordinate\n"
" --tile <hex> 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<std::string> 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<FewShotExample> PromptBuilder::GetExamplesForCategory(
const std::string& category) {
std::vector<FewShotExample> 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

View File

@@ -0,0 +1,74 @@
#ifndef YAZE_CLI_SERVICE_PROMPT_BUILDER_H_
#define YAZE_CLI_SERVICE_PROMPT_BUILDER_H_
#include <string>
#include <vector>
#include <map>
#include "absl/status/statusor.h"
namespace yaze {
namespace cli {
// Few-shot example for prompt engineering
struct FewShotExample {
std::string user_prompt;
std::vector<std::string> 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<std::string, std::string> 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<FewShotExample> 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<std::string, std::string> command_docs_; // Command name -> docs
std::vector<FewShotExample> examples_;
int verbosity_ = 1;
bool catalogue_loaded_ = false;
};
} // namespace cli
} // namespace yaze
#endif // YAZE_CLI_SERVICE_PROMPT_BUILDER_H_

View File

@@ -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