feat: Add PromptBuilder for enhanced command prompting and examples
This commit is contained in:
@@ -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() {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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();
|
||||
|
||||
300
src/cli/service/prompt_builder.cc
Normal file
300
src/cli/service/prompt_builder.cc
Normal 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
|
||||
74
src/cli/service/prompt_builder.h
Normal file
74
src/cli/service/prompt_builder.h
Normal 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_
|
||||
Reference in New Issue
Block a user