Add YAML support and enhance AI service context handling
- Integrated yaml-cpp library into the project for YAML file parsing. - Updated ConversationalAgentService to set ROM context in AI services. - Extended AIService interface with SetRomContext method for context injection. - Implemented SetRomContext in GeminiAIService and OllamaAIService. - Enhanced PromptBuilder to load resource catalogues from YAML files. - Added functions to parse commands, tools, examples, and tile references from YAML. - Improved error handling for loading YAML files and added search paths for catalogues. - Updated CMake configuration to fetch yaml-cpp if not found. - Modified vcpkg.json to include yaml-cpp as a dependency.
This commit is contained in:
192
assets/agent/prompt_catalogue.yaml
Normal file
192
assets/agent/prompt_catalogue.yaml
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
commands:
|
||||||
|
palette export: |-
|
||||||
|
Export palette data to JSON file
|
||||||
|
--group <group> Palette group (overworld, dungeon, sprite)
|
||||||
|
--id <id> Palette ID (0-based index)
|
||||||
|
--to <file> Output JSON file path
|
||||||
|
palette import: |-
|
||||||
|
Import palette data from JSON file
|
||||||
|
--group <group> Palette group (overworld, dungeon, sprite)
|
||||||
|
--id <id> Palette ID (0-based index)
|
||||||
|
--from <file> Input JSON file path
|
||||||
|
palette set-color: |-
|
||||||
|
Modify a color in palette JSON file
|
||||||
|
--file <file> Palette JSON file to modify
|
||||||
|
--index <index> Color index (0-15 per palette)
|
||||||
|
--color <hex> New color in hex (0xRRGGBB format)
|
||||||
|
overworld set-tile: |-
|
||||||
|
Place a tile in the overworld
|
||||||
|
--map <id> Map ID (0-based)
|
||||||
|
--x <x> X coordinate (0-63)
|
||||||
|
--y <y> Y coordinate (0-63)
|
||||||
|
--tile <hex> Tile ID in hex (e.g., 0x02E for tree)
|
||||||
|
sprite set-position: |-
|
||||||
|
Move a sprite to a new position
|
||||||
|
--id <id> Sprite ID
|
||||||
|
--x <x> X coordinate
|
||||||
|
--y <y> Y coordinate
|
||||||
|
dungeon set-room-tile: |-
|
||||||
|
Place a tile in a dungeon room
|
||||||
|
--room <id> Room ID
|
||||||
|
--x <x> X coordinate
|
||||||
|
--y <y> Y coordinate
|
||||||
|
--tile <hex> Tile ID
|
||||||
|
rom validate: "Validate ROM integrity and structure"
|
||||||
|
|
||||||
|
tools:
|
||||||
|
- name: resource-list
|
||||||
|
description: "List project-defined resource labels for the requested category."
|
||||||
|
usage_notes: "Use this whenever you need to reference project-specific labels or IDs from the ROM."
|
||||||
|
arguments:
|
||||||
|
- name: type
|
||||||
|
description: "Resource category (dungeon, sprite, overworld, entrance, room, etc.)."
|
||||||
|
required: true
|
||||||
|
example: dungeon
|
||||||
|
- name: format
|
||||||
|
description: "Response format (json or table). Defaults to JSON if omitted."
|
||||||
|
required: false
|
||||||
|
example: json
|
||||||
|
- name: dungeon-list-sprites
|
||||||
|
description: "Inspect sprite placements for a specific dungeon room."
|
||||||
|
usage_notes: "Returns sprite IDs, positions, and metadata for the requested room."
|
||||||
|
arguments:
|
||||||
|
- name: room
|
||||||
|
description: "Room label or numeric ID (supports hex like 0x123)."
|
||||||
|
required: true
|
||||||
|
example: hyrule_castle_throne
|
||||||
|
- name: dungeon
|
||||||
|
description: "Optional dungeon ID when room names are ambiguous."
|
||||||
|
required: false
|
||||||
|
example: 0x00
|
||||||
|
- name: format
|
||||||
|
description: "Response format (json or table). Defaults to JSON if omitted."
|
||||||
|
required: false
|
||||||
|
example: json
|
||||||
|
- name: overworld-find-tile
|
||||||
|
description: "Search all overworld maps for occurrences of a specific tile16 ID."
|
||||||
|
usage_notes: "Ideal for tile lookup questions. Includes coordinates for each match."
|
||||||
|
arguments:
|
||||||
|
- name: tile
|
||||||
|
description: "Tile16 ID to search for (accepts hex or decimal)."
|
||||||
|
required: true
|
||||||
|
example: 0x02E
|
||||||
|
- name: map
|
||||||
|
description: "Optional map ID filter (0=Light World, 1=Dark World, etc.)."
|
||||||
|
required: false
|
||||||
|
example: 0
|
||||||
|
- name: format
|
||||||
|
description: "Response format (json or table). Defaults to JSON if omitted."
|
||||||
|
required: false
|
||||||
|
example: json
|
||||||
|
- name: overworld-describe-map
|
||||||
|
description: "Summarize metadata for an overworld map, including regions and labels."
|
||||||
|
usage_notes: "Use this before proposing edits to understand map properties and labels."
|
||||||
|
arguments:
|
||||||
|
- name: map
|
||||||
|
description: "Map ID or label to describe."
|
||||||
|
required: true
|
||||||
|
example: 0
|
||||||
|
- name: format
|
||||||
|
description: "Response format (json or table). Defaults to JSON if omitted."
|
||||||
|
required: false
|
||||||
|
example: json
|
||||||
|
- name: overworld-list-warps
|
||||||
|
description: "List warp entrances and exits for overworld maps, including destinations."
|
||||||
|
usage_notes: "Helpful for navigation questions and verifying warp destinations."
|
||||||
|
arguments:
|
||||||
|
- name: map
|
||||||
|
description: "Optional map filter. Defaults to all maps when omitted."
|
||||||
|
required: false
|
||||||
|
example: 1
|
||||||
|
- name: format
|
||||||
|
description: "Response format (json or table). Defaults to JSON if omitted."
|
||||||
|
required: false
|
||||||
|
example: json
|
||||||
|
|
||||||
|
tile16_reference:
|
||||||
|
grass: 0x020
|
||||||
|
dirt: 0x022
|
||||||
|
tree: 0x02E
|
||||||
|
bush: 0x003
|
||||||
|
rock: 0x004
|
||||||
|
flower: 0x021
|
||||||
|
sand: 0x023
|
||||||
|
water_top: 0x14C
|
||||||
|
water_middle: 0x14D
|
||||||
|
water_bottom: 0x14E
|
||||||
|
deep_water: 0x14F
|
||||||
|
shallow_water: 0x150
|
||||||
|
water_single: 0x038
|
||||||
|
house_00: 0x0C0
|
||||||
|
house_01: 0x0C1
|
||||||
|
house_10: 0x0D0
|
||||||
|
house_11: 0x0D1
|
||||||
|
|
||||||
|
examples:
|
||||||
|
- user_prompt: "Place a tree at position 10, 20 on the Light World map"
|
||||||
|
text_response: "Okay, I can place that tree for you. Here is the command:"
|
||||||
|
reasoning: "Single tile16 placement. Tree tile ID is 0x02E in vanilla ALTTP"
|
||||||
|
commands:
|
||||||
|
- "overworld set-tile --map 0 --x 10 --y 20 --tile 0x02E"
|
||||||
|
- user_prompt: "Create a 3x3 water pond at coordinates 15, 10"
|
||||||
|
text_response: "Creating a 3x3 pond requires nine `set-tile` commands. Here they are:"
|
||||||
|
reasoning: "Water areas use different edge tiles: 0x14C (top), 0x14D (middle), 0x14E (bottom)"
|
||||||
|
commands:
|
||||||
|
- "overworld set-tile --map 0 --x 15 --y 10 --tile 0x14C"
|
||||||
|
- "overworld set-tile --map 0 --x 16 --y 10 --tile 0x14D"
|
||||||
|
- "overworld set-tile --map 0 --x 17 --y 10 --tile 0x14C"
|
||||||
|
- "overworld set-tile --map 0 --x 15 --y 11 --tile 0x14D"
|
||||||
|
- "overworld set-tile --map 0 --x 16 --y 11 --tile 0x14D"
|
||||||
|
- "overworld set-tile --map 0 --x 17 --y 11 --tile 0x14D"
|
||||||
|
- "overworld set-tile --map 0 --x 15 --y 12 --tile 0x14E"
|
||||||
|
- "overworld set-tile --map 0 --x 16 --y 12 --tile 0x14E"
|
||||||
|
- "overworld set-tile --map 0 --x 17 --y 12 --tile 0x14E"
|
||||||
|
- user_prompt: "Add a dirt path from position 5,5 to 5,15"
|
||||||
|
text_response: "I will generate a `set-tile` command for each point along the path."
|
||||||
|
reasoning: "Linear paths are created by placing tiles sequentially. Dirt tile is 0x022"
|
||||||
|
commands:
|
||||||
|
- "overworld set-tile --map 0 --x 5 --y 5 --tile 0x022"
|
||||||
|
- "overworld set-tile --map 0 --x 5 --y 6 --tile 0x022"
|
||||||
|
- "overworld set-tile --map 0 --x 5 --y 7 --tile 0x022"
|
||||||
|
- "overworld set-tile --map 0 --x 5 --y 8 --tile 0x022"
|
||||||
|
- "overworld set-tile --map 0 --x 5 --y 9 --tile 0x022"
|
||||||
|
- "overworld set-tile --map 0 --x 5 --y 10 --tile 0x022"
|
||||||
|
- "overworld set-tile --map 0 --x 5 --y 11 --tile 0x022"
|
||||||
|
- "overworld set-tile --map 0 --x 5 --y 12 --tile 0x022"
|
||||||
|
- "overworld set-tile --map 0 --x 5 --y 13 --tile 0x022"
|
||||||
|
- "overworld set-tile --map 0 --x 5 --y 14 --tile 0x022"
|
||||||
|
- "overworld set-tile --map 0 --x 5 --y 15 --tile 0x022"
|
||||||
|
- user_prompt: "Plant a row of trees horizontally at y=8 from x=20 to x=25"
|
||||||
|
text_response: "Here are the commands to plant that row of trees:"
|
||||||
|
reasoning: "Tree rows create natural barriers and visual boundaries"
|
||||||
|
commands:
|
||||||
|
- "overworld set-tile --map 0 --x 20 --y 8 --tile 0x02E"
|
||||||
|
- "overworld set-tile --map 0 --x 21 --y 8 --tile 0x02E"
|
||||||
|
- "overworld set-tile --map 0 --x 22 --y 8 --tile 0x02E"
|
||||||
|
- "overworld set-tile --map 0 --x 23 --y 8 --tile 0x02E"
|
||||||
|
- "overworld set-tile --map 0 --x 24 --y 8 --tile 0x02E"
|
||||||
|
- "overworld set-tile --map 0 --x 25 --y 8 --tile 0x02E"
|
||||||
|
- user_prompt: "Add 3 soldiers to the Eastern Palace entrance room"
|
||||||
|
text_response: "I've identified the dungeon and sprite IDs from your project's labels. Here are the commands:"
|
||||||
|
reasoning: "Dungeon ID 0x02 is Eastern Palace. Sprite 0x41 is soldier. Spread placement for balance"
|
||||||
|
commands:
|
||||||
|
- "dungeon add-sprite --dungeon 0x02 --room 0x00 --sprite 0x41 --x 5 --y 3"
|
||||||
|
- "dungeon add-sprite --dungeon 0x02 --room 0x00 --sprite 0x41 --x 10 --y 3"
|
||||||
|
- "dungeon add-sprite --dungeon 0x02 --room 0x00 --sprite 0x41 --x 7 --y 8"
|
||||||
|
- user_prompt: "Place a chest in the Hyrule Castle treasure room"
|
||||||
|
text_response: "Certainly. I will place a chest containing a small key in the center of the room."
|
||||||
|
reasoning: "Dungeon 0x00 is Hyrule Castle. Item 0x12 is a small key. Position centered in room"
|
||||||
|
commands:
|
||||||
|
- "dungeon add-chest --dungeon 0x00 --room 0x60 --x 7 --y 5 --item 0x12 --big false"
|
||||||
|
- user_prompt: "Check if my overworld changes are valid"
|
||||||
|
text_response: "Yes, I can validate the ROM for you."
|
||||||
|
reasoning: "Validation ensures ROM integrity after tile modifications"
|
||||||
|
commands:
|
||||||
|
- "rom validate"
|
||||||
|
- user_prompt: "What dungeons are in this project?"
|
||||||
|
text_response: "I can list the dungeons for you. Let me check the resource labels."
|
||||||
|
reasoning: "The user is asking a question. I need to use the `resource-list` tool to find the answer."
|
||||||
|
tool_calls:
|
||||||
|
- tool_name: resource-list
|
||||||
|
args:
|
||||||
|
type: dungeon
|
||||||
@@ -24,6 +24,7 @@ target_link_libraries(yaze_agent
|
|||||||
PUBLIC
|
PUBLIC
|
||||||
yaze_common
|
yaze_common
|
||||||
${ABSL_TARGETS}
|
${ABSL_TARGETS}
|
||||||
|
yaml-cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
target_include_directories(yaze_agent
|
target_include_directories(yaze_agent
|
||||||
|
|||||||
@@ -152,6 +152,9 @@ ConversationalAgentService::ConversationalAgentService() {
|
|||||||
void ConversationalAgentService::SetRomContext(Rom* rom) {
|
void ConversationalAgentService::SetRomContext(Rom* rom) {
|
||||||
rom_context_ = rom;
|
rom_context_ = rom;
|
||||||
tool_dispatcher_.SetRomContext(rom_context_);
|
tool_dispatcher_.SetRomContext(rom_context_);
|
||||||
|
if (ai_service_) {
|
||||||
|
ai_service_->SetRomContext(rom_context_);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
absl::StatusOr<ChatMessage> ConversationalAgentService::SendMessage(
|
absl::StatusOr<ChatMessage> ConversationalAgentService::SendMessage(
|
||||||
|
|||||||
@@ -9,6 +9,8 @@
|
|||||||
#include "cli/service/ai/common.h"
|
#include "cli/service/ai/common.h"
|
||||||
|
|
||||||
namespace yaze {
|
namespace yaze {
|
||||||
|
class Rom;
|
||||||
|
|
||||||
namespace cli {
|
namespace cli {
|
||||||
namespace agent {
|
namespace agent {
|
||||||
struct ChatMessage;
|
struct ChatMessage;
|
||||||
@@ -18,6 +20,10 @@ class AIService {
|
|||||||
public:
|
public:
|
||||||
virtual ~AIService() = default;
|
virtual ~AIService() = default;
|
||||||
|
|
||||||
|
// Provide the AI service with the active ROM so prompts can include
|
||||||
|
// project-specific context.
|
||||||
|
virtual void SetRomContext(Rom* rom) { (void)rom; }
|
||||||
|
|
||||||
// Generate a response from a single prompt.
|
// Generate a response from a single prompt.
|
||||||
virtual absl::StatusOr<AgentResponse> GenerateResponse(
|
virtual absl::StatusOr<AgentResponse> GenerateResponse(
|
||||||
const std::string& prompt) = 0;
|
const std::string& prompt) = 0;
|
||||||
@@ -30,6 +36,7 @@ class AIService {
|
|||||||
// Mock implementation for testing
|
// Mock implementation for testing
|
||||||
class MockAIService : public AIService {
|
class MockAIService : public AIService {
|
||||||
public:
|
public:
|
||||||
|
void SetRomContext(Rom* rom) override { (void)rom; }
|
||||||
absl::StatusOr<AgentResponse> GenerateResponse(
|
absl::StatusOr<AgentResponse> GenerateResponse(
|
||||||
const std::string& prompt) override;
|
const std::string& prompt) override;
|
||||||
absl::StatusOr<AgentResponse> GenerateResponse(
|
absl::StatusOr<AgentResponse> GenerateResponse(
|
||||||
|
|||||||
@@ -21,7 +21,10 @@ namespace cli {
|
|||||||
GeminiAIService::GeminiAIService(const GeminiConfig& config)
|
GeminiAIService::GeminiAIService(const GeminiConfig& config)
|
||||||
: config_(config) {
|
: config_(config) {
|
||||||
// Load command documentation into prompt builder
|
// Load command documentation into prompt builder
|
||||||
prompt_builder_.LoadResourceCatalogue(""); // TODO: Pass actual yaml path when available
|
if (auto status = prompt_builder_.LoadResourceCatalogue(""); !status.ok()) {
|
||||||
|
std::cerr << "⚠️ Failed to load agent prompt catalogue: "
|
||||||
|
<< status.message() << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
if (config_.system_instruction.empty()) {
|
if (config_.system_instruction.empty()) {
|
||||||
// Use enhanced prompting by default
|
// Use enhanced prompting by default
|
||||||
@@ -39,6 +42,10 @@ std::string GeminiAIService::BuildSystemInstruction() {
|
|||||||
return prompt_builder_.BuildSystemInstruction();
|
return prompt_builder_.BuildSystemInstruction();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GeminiAIService::SetRomContext(Rom* rom) {
|
||||||
|
prompt_builder_.SetRom(rom);
|
||||||
|
}
|
||||||
|
|
||||||
absl::Status GeminiAIService::CheckAvailability() {
|
absl::Status GeminiAIService::CheckAvailability() {
|
||||||
#ifndef YAZE_WITH_JSON
|
#ifndef YAZE_WITH_JSON
|
||||||
return absl::UnimplementedError(
|
return absl::UnimplementedError(
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ struct GeminiConfig {
|
|||||||
class GeminiAIService : public AIService {
|
class GeminiAIService : public AIService {
|
||||||
public:
|
public:
|
||||||
explicit GeminiAIService(const GeminiConfig& config);
|
explicit GeminiAIService(const GeminiConfig& config);
|
||||||
|
void SetRomContext(Rom* rom) override;
|
||||||
|
|
||||||
// Primary interface
|
// Primary interface
|
||||||
absl::StatusOr<AgentResponse> GenerateResponse(
|
absl::StatusOr<AgentResponse> GenerateResponse(
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#include "cli/service/ai/ollama_ai_service.h"
|
#include "cli/service/ai/ollama_ai_service.h"
|
||||||
|
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
#include "absl/strings/str_cat.h"
|
#include "absl/strings/str_cat.h"
|
||||||
#include "absl/strings/str_format.h"
|
#include "absl/strings/str_format.h"
|
||||||
@@ -33,7 +34,10 @@ namespace cli {
|
|||||||
|
|
||||||
OllamaAIService::OllamaAIService(const OllamaConfig& config) : config_(config) {
|
OllamaAIService::OllamaAIService(const OllamaConfig& config) : config_(config) {
|
||||||
// Load command documentation into prompt builder
|
// Load command documentation into prompt builder
|
||||||
prompt_builder_.LoadResourceCatalogue(""); // TODO: Pass actual yaml path when available
|
if (auto status = prompt_builder_.LoadResourceCatalogue(""); !status.ok()) {
|
||||||
|
std::cerr << "⚠️ Failed to load agent prompt catalogue: "
|
||||||
|
<< status.message() << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
if (config_.system_prompt.empty()) {
|
if (config_.system_prompt.empty()) {
|
||||||
// Use enhanced prompting by default
|
// Use enhanced prompting by default
|
||||||
@@ -51,6 +55,10 @@ std::string OllamaAIService::BuildSystemPrompt() {
|
|||||||
return prompt_builder_.BuildSystemInstruction();
|
return prompt_builder_.BuildSystemInstruction();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void OllamaAIService::SetRomContext(Rom* rom) {
|
||||||
|
prompt_builder_.SetRom(rom);
|
||||||
|
}
|
||||||
|
|
||||||
absl::Status OllamaAIService::CheckAvailability() {
|
absl::Status OllamaAIService::CheckAvailability() {
|
||||||
#if !YAZE_HAS_HTTPLIB || !YAZE_HAS_JSON
|
#if !YAZE_HAS_HTTPLIB || !YAZE_HAS_JSON
|
||||||
return absl::UnimplementedError(
|
return absl::UnimplementedError(
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ class OllamaAIService : public AIService {
|
|||||||
public:
|
public:
|
||||||
explicit OllamaAIService(const OllamaConfig& config);
|
explicit OllamaAIService(const OllamaConfig& config);
|
||||||
|
|
||||||
|
void SetRomContext(Rom* rom) override;
|
||||||
|
|
||||||
// Generate z3ed commands from natural language prompt
|
// Generate z3ed commands from natural language prompt
|
||||||
absl::StatusOr<AgentResponse> GenerateResponse(
|
absl::StatusOr<AgentResponse> GenerateResponse(
|
||||||
const std::string& prompt) override;
|
const std::string& prompt) override;
|
||||||
|
|||||||
@@ -1,190 +1,365 @@
|
|||||||
#include "cli/service/ai/prompt_builder.h"
|
#include "cli/service/ai/prompt_builder.h"
|
||||||
#include "cli/service/agent/conversational_agent_service.h"
|
#include "cli/service/agent/conversational_agent_service.h"
|
||||||
|
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <filesystem>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
|
#include <iostream>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
|
||||||
#include "absl/strings/str_cat.h"
|
#include "absl/strings/str_cat.h"
|
||||||
#include "absl/strings/str_join.h"
|
#include "absl/strings/str_join.h"
|
||||||
|
#include "nlohmann/json.hpp"
|
||||||
|
#include "yaml-cpp/yaml.h"
|
||||||
|
|
||||||
namespace yaze {
|
namespace yaze {
|
||||||
namespace cli {
|
namespace cli {
|
||||||
|
|
||||||
PromptBuilder::PromptBuilder() {
|
namespace {
|
||||||
LoadDefaultExamples();
|
|
||||||
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
|
nlohmann::json YamlToJson(const YAML::Node& node) {
|
||||||
|
if (!node) {
|
||||||
|
return nlohmann::json();
|
||||||
}
|
}
|
||||||
|
|
||||||
void PromptBuilder::LoadDefaultExamples() {
|
switch (node.Type()) {
|
||||||
// ==========================================================================
|
case YAML::NodeType::Scalar:
|
||||||
// OVERWORLD TILE16 EDITING - Primary Focus
|
return node.as<std::string>("");
|
||||||
// ==========================================================================
|
case YAML::NodeType::Sequence: {
|
||||||
|
nlohmann::json array = nlohmann::json::array();
|
||||||
|
for (const auto& item : node) {
|
||||||
|
array.push_back(YamlToJson(item));
|
||||||
|
}
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
case YAML::NodeType::Map: {
|
||||||
|
nlohmann::json object = nlohmann::json::object();
|
||||||
|
for (const auto& kv : node) {
|
||||||
|
object[kv.first.as<std::string>()] = YamlToJson(kv.second);
|
||||||
|
}
|
||||||
|
return object;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return nlohmann::json();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Single tile placement
|
std::vector<fs::path> BuildCatalogueSearchPaths(const std::string& explicit_path) {
|
||||||
examples_.push_back({
|
std::vector<fs::path> paths;
|
||||||
"Place a tree at position 10, 20 on the Light World map",
|
if (!explicit_path.empty()) {
|
||||||
"Okay, I can place that tree for you. Here is the command:",
|
paths.emplace_back(explicit_path);
|
||||||
{"overworld set-tile --map 0 --x 10 --y 20 --tile 0x02E"},
|
}
|
||||||
"Single tile16 placement. Tree tile ID is 0x02E in vanilla ALTTP"});
|
|
||||||
|
|
||||||
// Area/region editing
|
if (const char* env_path = std::getenv("YAZE_AGENT_CATALOGUE")) {
|
||||||
examples_.push_back({
|
if (*env_path != '\0') {
|
||||||
"Create a 3x3 water pond at coordinates 15, 10",
|
paths.emplace_back(env_path);
|
||||||
"Creating a 3x3 pond requires nine `set-tile` commands. Here they are:",
|
}
|
||||||
{"overworld set-tile --map 0 --x 15 --y 10 --tile 0x14C",
|
}
|
||||||
"overworld set-tile --map 0 --x 16 --y 10 --tile 0x14D",
|
|
||||||
"overworld set-tile --map 0 --x 17 --y 10 --tile 0x14C",
|
|
||||||
"overworld set-tile --map 0 --x 15 --y 11 --tile 0x14D",
|
|
||||||
"overworld set-tile --map 0 --x 16 --y 11 --tile 0x14D",
|
|
||||||
"overworld set-tile --map 0 --x 17 --y 11 --tile 0x14D",
|
|
||||||
"overworld set-tile --map 0 --x 15 --y 12 --tile 0x14E",
|
|
||||||
"overworld set-tile --map 0 --x 16 --y 12 --tile 0x14E",
|
|
||||||
"overworld set-tile --map 0 --x 17 --y 12 --tile 0x14E"},
|
|
||||||
"Water areas use different edge tiles: 0x14C (top), 0x14D (middle), "
|
|
||||||
"0x14E (bottom)"});
|
|
||||||
|
|
||||||
// Path/line creation
|
const std::vector<std::string> defaults = {
|
||||||
examples_.push_back(
|
"assets/agent/prompt_catalogue.yaml",
|
||||||
{"Add a dirt path from position 5,5 to 5,15",
|
"../assets/agent/prompt_catalogue.yaml",
|
||||||
"I will generate a `set-tile` command for each point along the path.",
|
"../../assets/agent/prompt_catalogue.yaml",
|
||||||
{"overworld set-tile --map 0 --x 5 --y 5 --tile 0x022",
|
"assets/z3ed/prompt_catalogue.yaml",
|
||||||
"overworld set-tile --map 0 --x 5 --y 6 --tile 0x022",
|
"../assets/z3ed/prompt_catalogue.yaml",
|
||||||
"overworld set-tile --map 0 --x 5 --y 7 --tile 0x022",
|
};
|
||||||
"overworld set-tile --map 0 --x 5 --y 8 --tile 0x022",
|
|
||||||
"overworld set-tile --map 0 --x 5 --y 9 --tile 0x022",
|
|
||||||
"overworld set-tile --map 0 --x 5 --y 10 --tile 0x022",
|
|
||||||
"overworld set-tile --map 0 --x 5 --y 11 --tile 0x022",
|
|
||||||
"overworld set-tile --map 0 --x 5 --y 12 --tile 0x022",
|
|
||||||
"overworld set-tile --map 0 --x 5 --y 13 --tile 0x022",
|
|
||||||
"overworld set-tile --map 0 --x 5 --y 14 --tile 0x022",
|
|
||||||
"overworld set-tile --map 0 --x 5 --y 15 --tile 0x022"},
|
|
||||||
"Linear paths are created by placing tiles sequentially. Dirt tile is "
|
|
||||||
"0x022"});
|
|
||||||
|
|
||||||
// Forest/tree grouping
|
for (const auto& candidate : defaults) {
|
||||||
examples_.push_back(
|
paths.emplace_back(candidate);
|
||||||
{"Plant a row of trees horizontally at y=8 from x=20 to x=25",
|
}
|
||||||
"Here are the commands to plant that row of trees:",
|
|
||||||
{"overworld set-tile --map 0 --x 20 --y 8 --tile 0x02E",
|
|
||||||
"overworld set-tile --map 0 --x 21 --y 8 --tile 0x02E",
|
|
||||||
"overworld set-tile --map 0 --x 22 --y 8 --tile 0x02E",
|
|
||||||
"overworld set-tile --map 0 --x 23 --y 8 --tile 0x02E",
|
|
||||||
"overworld set-tile --map 0 --x 24 --y 8 --tile 0x02E",
|
|
||||||
"overworld set-tile --map 0 --x 25 --y 8 --tile 0x02E"},
|
|
||||||
"Tree rows create natural barriers and visual boundaries"});
|
|
||||||
|
|
||||||
// ==========================================================================
|
return paths;
|
||||||
// DUNGEON EDITING - Label-Aware Operations
|
}
|
||||||
// ==========================================================================
|
|
||||||
|
|
||||||
// Sprite placement (label-aware)
|
} // namespace
|
||||||
examples_.push_back(
|
|
||||||
{"Add 3 soldiers to the Eastern Palace entrance room",
|
|
||||||
"I've identified the dungeon and sprite IDs from your project's "
|
|
||||||
"labels. Here are the commands:",
|
|
||||||
{"dungeon add-sprite --dungeon 0x02 --room 0x00 --sprite 0x41 --x 5 --y "
|
|
||||||
"3",
|
|
||||||
"dungeon add-sprite --dungeon 0x02 --room 0x00 --sprite 0x41 --x 10 "
|
|
||||||
"--y 3",
|
|
||||||
"dungeon add-sprite --dungeon 0x02 --room 0x00 --sprite 0x41 --x 7 --y "
|
|
||||||
"8"},
|
|
||||||
"Dungeon ID 0x02 is Eastern Palace. Sprite 0x41 is soldier. Spread "
|
|
||||||
"placement for balance"});
|
|
||||||
|
|
||||||
// Object placement
|
PromptBuilder::PromptBuilder() = default;
|
||||||
examples_.push_back(
|
|
||||||
{"Place a chest in the Hyrule Castle treasure room",
|
|
||||||
"Certainly. I will place a chest containing a small key in the center of "
|
|
||||||
"the room.",
|
|
||||||
{"dungeon add-chest --dungeon 0x00 --room 0x60 --x 7 --y 5 --item 0x12 "
|
|
||||||
"--big false"},
|
|
||||||
"Dungeon 0x00 is Hyrule Castle. Item 0x12 is a small key. Position "
|
|
||||||
"centered in room"});
|
|
||||||
|
|
||||||
// ==========================================================================
|
void PromptBuilder::ClearCatalogData() {
|
||||||
// COMMON TILE16 REFERENCE (for AI knowledge)
|
command_docs_.clear();
|
||||||
// ==========================================================================
|
examples_.clear();
|
||||||
// Grass: 0x020
|
tool_specs_.clear();
|
||||||
// Dirt: 0x022
|
tile_reference_.clear();
|
||||||
// Tree: 0x02E
|
catalogue_loaded_ = false;
|
||||||
// Water (top): 0x14C
|
}
|
||||||
// Water (middle): 0x14D
|
|
||||||
// Water (bottom): 0x14E
|
|
||||||
// Bush: 0x003
|
|
||||||
// Rock: 0x004
|
|
||||||
// Flower: 0x021
|
|
||||||
// Sand: 0x023
|
|
||||||
// Deep Water: 0x14F
|
|
||||||
// Shallow Water: 0x150
|
|
||||||
|
|
||||||
// Validation example (still useful)
|
absl::StatusOr<std::string> PromptBuilder::ResolveCataloguePath(
|
||||||
examples_.push_back(
|
const std::string& yaml_path) const {
|
||||||
{"Check if my overworld changes are valid",
|
const auto search_paths = BuildCatalogueSearchPaths(yaml_path);
|
||||||
"Yes, I can validate the ROM for you.",
|
for (const auto& candidate : search_paths) {
|
||||||
{"rom validate"},
|
fs::path resolved = candidate;
|
||||||
"Validation ensures ROM integrity after tile modifications"});
|
if (resolved.is_relative()) {
|
||||||
|
resolved = fs::absolute(resolved);
|
||||||
|
}
|
||||||
|
if (fs::exists(resolved)) {
|
||||||
|
return resolved.string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ==========================================================================
|
return absl::NotFoundError(
|
||||||
// Q&A / Tool Use
|
absl::StrCat("Prompt catalogue not found. Checked paths: ",
|
||||||
// ==========================================================================
|
absl::StrJoin(search_paths, ", ",
|
||||||
examples_.push_back(
|
[](std::string* out, const fs::path& path) {
|
||||||
{"What dungeons are in this project?",
|
absl::StrAppend(out, path.string());
|
||||||
"I can list the dungeons for you. Let me check the resource labels.",
|
})));
|
||||||
{},
|
|
||||||
"The user is asking a question. I need to use the `resource-list` tool "
|
|
||||||
"to find the answer.",
|
|
||||||
{{"resource-list", {{"type", "dungeon"}}}}});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
absl::Status PromptBuilder::LoadResourceCatalogue(const std::string& yaml_path) {
|
absl::Status PromptBuilder::LoadResourceCatalogue(const std::string& yaml_path) {
|
||||||
// TODO: Parse z3ed-resources.yaml when available
|
auto resolved_or = ResolveCataloguePath(yaml_path);
|
||||||
// For now, use hardcoded command reference
|
if (!resolved_or.ok()) {
|
||||||
|
ClearCatalogData();
|
||||||
|
return resolved_or.status();
|
||||||
|
}
|
||||||
|
|
||||||
command_docs_["palette export"] =
|
const std::string& resolved_path = resolved_or.value();
|
||||||
"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"] =
|
YAML::Node root;
|
||||||
"Import palette data from JSON file\n"
|
try {
|
||||||
" --group <group> Palette group (overworld, dungeon, sprite)\n"
|
root = YAML::LoadFile(resolved_path);
|
||||||
" --id <id> Palette ID (0-based index)\n"
|
} catch (const YAML::BadFile& e) {
|
||||||
" --from <file> Input JSON file path";
|
ClearCatalogData();
|
||||||
|
return absl::NotFoundError(
|
||||||
|
absl::StrCat("Unable to open prompt catalogue at ", resolved_path,
|
||||||
|
": ", e.what()));
|
||||||
|
} catch (const YAML::ParserException& e) {
|
||||||
|
ClearCatalogData();
|
||||||
|
return absl::InvalidArgumentError(
|
||||||
|
absl::StrCat("Failed to parse prompt catalogue at ", resolved_path,
|
||||||
|
": ", e.what()));
|
||||||
|
}
|
||||||
|
|
||||||
command_docs_["palette set-color"] =
|
nlohmann::json catalogue = YamlToJson(root);
|
||||||
"Modify a color in palette JSON file\n"
|
ClearCatalogData();
|
||||||
" --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"] =
|
if (catalogue.contains("commands")) {
|
||||||
"Place a tile in the overworld\n"
|
if (auto status = ParseCommands(catalogue["commands"]); !status.ok()) {
|
||||||
" --map <id> Map ID (0-based)\n"
|
return status;
|
||||||
" --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"] =
|
if (catalogue.contains("tools")) {
|
||||||
"Move a sprite to new position\n"
|
if (auto status = ParseTools(catalogue["tools"]); !status.ok()) {
|
||||||
" --id <id> Sprite ID\n"
|
return status;
|
||||||
" --x <x> X coordinate\n"
|
}
|
||||||
" --y <y> Y coordinate";
|
}
|
||||||
|
|
||||||
command_docs_["dungeon set-room-tile"] =
|
if (catalogue.contains("examples")) {
|
||||||
"Place a tile in dungeon room\n"
|
if (auto status = ParseExamples(catalogue["examples"]); !status.ok()) {
|
||||||
" --room <id> Room ID\n"
|
return status;
|
||||||
" --x <x> X coordinate\n"
|
}
|
||||||
" --y <y> Y coordinate\n"
|
}
|
||||||
" --tile <hex> Tile ID";
|
|
||||||
|
|
||||||
command_docs_["rom validate"] =
|
if (catalogue.contains("tile16_reference")) {
|
||||||
"Validate ROM integrity and structure";
|
ParseTileReference(catalogue["tile16_reference"]);
|
||||||
|
}
|
||||||
|
|
||||||
catalogue_loaded_ = true;
|
catalogue_loaded_ = true;
|
||||||
return absl::OkStatus();
|
return absl::OkStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string PromptBuilder::BuildCommandReference() {
|
absl::Status PromptBuilder::ParseCommands(const nlohmann::json& commands) {
|
||||||
|
if (!commands.is_object()) {
|
||||||
|
return absl::InvalidArgumentError(
|
||||||
|
"commands section must be an object mapping command names to docs");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& [name, value] : commands.items()) {
|
||||||
|
if (!value.is_string()) {
|
||||||
|
return absl::InvalidArgumentError(
|
||||||
|
absl::StrCat("Command entry for ", name, " must be a string"));
|
||||||
|
}
|
||||||
|
command_docs_[name] = value.get<std::string>();
|
||||||
|
}
|
||||||
|
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status PromptBuilder::ParseTools(const nlohmann::json& tools) {
|
||||||
|
if (!tools.is_array()) {
|
||||||
|
return absl::InvalidArgumentError("tools section must be an array");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& tool_json : tools) {
|
||||||
|
if (!tool_json.is_object()) {
|
||||||
|
return absl::InvalidArgumentError(
|
||||||
|
"Each tool entry must be an object with name/description");
|
||||||
|
}
|
||||||
|
|
||||||
|
ToolSpecification spec;
|
||||||
|
if (tool_json.contains("name") && tool_json["name"].is_string()) {
|
||||||
|
spec.name = tool_json["name"].get<std::string>();
|
||||||
|
} else {
|
||||||
|
return absl::InvalidArgumentError("Tool entry missing name");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tool_json.contains("description") && tool_json["description"].is_string()) {
|
||||||
|
spec.description = tool_json["description"].get<std::string>();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tool_json.contains("usage_notes") && tool_json["usage_notes"].is_string()) {
|
||||||
|
spec.usage_notes = tool_json["usage_notes"].get<std::string>();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tool_json.contains("arguments")) {
|
||||||
|
const auto& args = tool_json["arguments"];
|
||||||
|
if (!args.is_array()) {
|
||||||
|
return absl::InvalidArgumentError(
|
||||||
|
absl::StrCat("Tool arguments for ", spec.name, " must be an array"));
|
||||||
|
}
|
||||||
|
for (const auto& arg_json : args) {
|
||||||
|
if (!arg_json.is_object()) {
|
||||||
|
return absl::InvalidArgumentError(
|
||||||
|
absl::StrCat("Argument entries for ", spec.name,
|
||||||
|
" must be objects"));
|
||||||
|
}
|
||||||
|
ToolArgument arg;
|
||||||
|
if (arg_json.contains("name") && arg_json["name"].is_string()) {
|
||||||
|
arg.name = arg_json["name"].get<std::string>();
|
||||||
|
} else {
|
||||||
|
return absl::InvalidArgumentError(
|
||||||
|
absl::StrCat("Argument entry for ", spec.name,
|
||||||
|
" is missing a name"));
|
||||||
|
}
|
||||||
|
if (arg_json.contains("description") && arg_json["description"].is_string()) {
|
||||||
|
arg.description = arg_json["description"].get<std::string>();
|
||||||
|
}
|
||||||
|
if (arg_json.contains("required")) {
|
||||||
|
if (!arg_json["required"].is_boolean()) {
|
||||||
|
return absl::InvalidArgumentError(
|
||||||
|
absl::StrCat("Argument 'required' flag for ", spec.name,
|
||||||
|
"::", arg.name, " must be boolean"));
|
||||||
|
}
|
||||||
|
arg.required = arg_json["required"].get<bool>();
|
||||||
|
}
|
||||||
|
if (arg_json.contains("example") && arg_json["example"].is_string()) {
|
||||||
|
arg.example = arg_json["example"].get<std::string>();
|
||||||
|
}
|
||||||
|
spec.arguments.push_back(std::move(arg));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tool_specs_.push_back(std::move(spec));
|
||||||
|
}
|
||||||
|
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status PromptBuilder::ParseExamples(const nlohmann::json& examples) {
|
||||||
|
if (!examples.is_array()) {
|
||||||
|
return absl::InvalidArgumentError("examples section must be an array");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& example_json : examples) {
|
||||||
|
if (!example_json.is_object()) {
|
||||||
|
return absl::InvalidArgumentError("Each example entry must be an object");
|
||||||
|
}
|
||||||
|
|
||||||
|
FewShotExample example;
|
||||||
|
if (example_json.contains("user_prompt") &&
|
||||||
|
example_json["user_prompt"].is_string()) {
|
||||||
|
example.user_prompt = example_json["user_prompt"].get<std::string>();
|
||||||
|
} else {
|
||||||
|
return absl::InvalidArgumentError("Example missing user_prompt");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (example_json.contains("text_response") &&
|
||||||
|
example_json["text_response"].is_string()) {
|
||||||
|
example.text_response = example_json["text_response"].get<std::string>();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (example_json.contains("reasoning") &&
|
||||||
|
example_json["reasoning"].is_string()) {
|
||||||
|
example.explanation = example_json["reasoning"].get<std::string>();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (example_json.contains("commands")) {
|
||||||
|
const auto& commands = example_json["commands"];
|
||||||
|
if (!commands.is_array()) {
|
||||||
|
return absl::InvalidArgumentError(
|
||||||
|
absl::StrCat("Example commands for ", example.user_prompt,
|
||||||
|
" must be an array"));
|
||||||
|
}
|
||||||
|
for (const auto& cmd : commands) {
|
||||||
|
if (!cmd.is_string()) {
|
||||||
|
return absl::InvalidArgumentError(
|
||||||
|
absl::StrCat("Command entries for ", example.user_prompt,
|
||||||
|
" must be strings"));
|
||||||
|
}
|
||||||
|
example.expected_commands.push_back(cmd.get<std::string>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (example_json.contains("tool_calls")) {
|
||||||
|
const auto& calls = example_json["tool_calls"];
|
||||||
|
if (!calls.is_array()) {
|
||||||
|
return absl::InvalidArgumentError(
|
||||||
|
absl::StrCat("Tool calls for ", example.user_prompt,
|
||||||
|
" must be an array"));
|
||||||
|
}
|
||||||
|
for (const auto& call_json : calls) {
|
||||||
|
if (!call_json.is_object()) {
|
||||||
|
return absl::InvalidArgumentError(
|
||||||
|
absl::StrCat("Tool call entries for ", example.user_prompt,
|
||||||
|
" must be objects"));
|
||||||
|
}
|
||||||
|
ToolCall call;
|
||||||
|
if (call_json.contains("tool_name") && call_json["tool_name"].is_string()) {
|
||||||
|
call.tool_name = call_json["tool_name"].get<std::string>();
|
||||||
|
} else {
|
||||||
|
return absl::InvalidArgumentError(
|
||||||
|
absl::StrCat("Tool call missing tool_name in example: ",
|
||||||
|
example.user_prompt));
|
||||||
|
}
|
||||||
|
if (call_json.contains("args")) {
|
||||||
|
const auto& args = call_json["args"];
|
||||||
|
if (!args.is_object()) {
|
||||||
|
return absl::InvalidArgumentError(
|
||||||
|
absl::StrCat("Tool call args for ", example.user_prompt,
|
||||||
|
" must be an object"));
|
||||||
|
}
|
||||||
|
for (const auto& [key, value] : args.items()) {
|
||||||
|
if (!value.is_string()) {
|
||||||
|
return absl::InvalidArgumentError(
|
||||||
|
absl::StrCat("Tool call arg value for ", example.user_prompt,
|
||||||
|
" must be a string"));
|
||||||
|
}
|
||||||
|
call.args[key] = value.get<std::string>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
example.tool_calls.push_back(std::move(call));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
example.explanation = example_json.value("explanation", example.explanation);
|
||||||
|
examples_.push_back(std::move(example));
|
||||||
|
}
|
||||||
|
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PromptBuilder::ParseTileReference(const nlohmann::json& tile_reference) {
|
||||||
|
if (!tile_reference.is_object()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& [alias, value] : tile_reference.items()) {
|
||||||
|
if (value.is_string()) {
|
||||||
|
tile_reference_[alias] = value.get<std::string>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string PromptBuilder::LookupTileId(const std::string& alias) const {
|
||||||
|
auto it = tile_reference_.find(alias);
|
||||||
|
if (it != tile_reference_.end()) {
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string PromptBuilder::BuildCommandReference() const {
|
||||||
std::ostringstream oss;
|
std::ostringstream oss;
|
||||||
|
|
||||||
oss << "# Available z3ed Commands\n\n";
|
oss << "# Available z3ed Commands\n\n";
|
||||||
@@ -197,7 +372,41 @@ std::string PromptBuilder::BuildCommandReference() {
|
|||||||
return oss.str();
|
return oss.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string PromptBuilder::BuildFewShotExamplesSection() {
|
std::string PromptBuilder::BuildToolReference() const {
|
||||||
|
if (tool_specs_.empty()) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::ostringstream oss;
|
||||||
|
oss << "# Available Agent Tools\n\n";
|
||||||
|
|
||||||
|
for (const auto& spec : tool_specs_) {
|
||||||
|
oss << "## " << spec.name << "\n";
|
||||||
|
if (!spec.description.empty()) {
|
||||||
|
oss << spec.description << "\n\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!spec.arguments.empty()) {
|
||||||
|
oss << "| Argument | Required | Description | Example |\n";
|
||||||
|
oss << "| --- | --- | --- | --- |\n";
|
||||||
|
for (const auto& arg : spec.arguments) {
|
||||||
|
oss << "| `" << arg.name << "` | " << (arg.required ? "yes" : "no")
|
||||||
|
<< " | " << arg.description << " | "
|
||||||
|
<< (arg.example.empty() ? "" : "`" + arg.example + "`")
|
||||||
|
<< " |\n";
|
||||||
|
}
|
||||||
|
oss << "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!spec.usage_notes.empty()) {
|
||||||
|
oss << "_Usage:_ " << spec.usage_notes << "\n\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return oss.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string PromptBuilder::BuildFewShotExamplesSection() const {
|
||||||
std::ostringstream oss;
|
std::ostringstream oss;
|
||||||
|
|
||||||
oss << "# Example Command Sequences\n\n";
|
oss << "# Example Command Sequences\n\n";
|
||||||
@@ -205,39 +414,42 @@ std::string PromptBuilder::BuildFewShotExamplesSection() {
|
|||||||
|
|
||||||
for (const auto& example : examples_) {
|
for (const auto& example : examples_) {
|
||||||
oss << "**User Request:** \"" << example.user_prompt << "\"\n";
|
oss << "**User Request:** \"" << example.user_prompt << "\"\n";
|
||||||
oss << "**Commands:**\n";
|
oss << "**Structured Response:**\n";
|
||||||
oss << "```json\n{";
|
|
||||||
oss << " \"text_response\": \"" << example.text_response << "\",\n";
|
nlohmann::json example_json = nlohmann::json::object();
|
||||||
oss << " \"tool_calls\": [";
|
if (!example.text_response.empty()) {
|
||||||
std::vector<std::string> tool_calls;
|
example_json["text_response"] = example.text_response;
|
||||||
|
}
|
||||||
|
if (!example.expected_commands.empty()) {
|
||||||
|
example_json["commands"] = example.expected_commands;
|
||||||
|
}
|
||||||
|
if (!example.explanation.empty()) {
|
||||||
|
example_json["reasoning"] = example.explanation;
|
||||||
|
}
|
||||||
|
if (!example.tool_calls.empty()) {
|
||||||
|
nlohmann::json calls = nlohmann::json::array();
|
||||||
for (const auto& call : example.tool_calls) {
|
for (const auto& call : example.tool_calls) {
|
||||||
std::vector<std::string> args;
|
nlohmann::json call_json;
|
||||||
|
call_json["tool_name"] = call.tool_name;
|
||||||
|
nlohmann::json args = nlohmann::json::object();
|
||||||
for (const auto& [key, value] : call.args) {
|
for (const auto& [key, value] : call.args) {
|
||||||
args.push_back("\"" + key + "\": \"" + value + "\"");
|
args[key] = value;
|
||||||
}
|
}
|
||||||
tool_calls.push_back("{\"tool_name\": \"" + call.tool_name +
|
call_json["args"] = std::move(args);
|
||||||
"\", \"args\": {" + absl::StrJoin(args, ", ") + "}}");
|
calls.push_back(std::move(call_json));
|
||||||
|
}
|
||||||
|
example_json["tool_calls"] = std::move(calls);
|
||||||
}
|
}
|
||||||
oss << absl::StrJoin(tool_calls, ", ");
|
|
||||||
oss << "],\n";
|
|
||||||
oss << " \"commands\": [";
|
|
||||||
|
|
||||||
std::vector<std::string> quoted_cmds;
|
oss << "```json\n" << example_json.dump(2) << "\n```\n\n";
|
||||||
for (const auto& cmd : example.expected_commands) {
|
|
||||||
quoted_cmds.push_back("\"" + cmd + "\"");
|
|
||||||
}
|
|
||||||
oss << absl::StrJoin(quoted_cmds, ", ");
|
|
||||||
|
|
||||||
oss << "],\n";
|
|
||||||
oss << " \"reasoning\": \"" << example.explanation << "\"\n";
|
|
||||||
oss << "}\n```\n\n";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return oss.str();
|
return oss.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string PromptBuilder::BuildConstraintsSection() {
|
std::string PromptBuilder::BuildConstraintsSection() const {
|
||||||
return R"(
|
std::ostringstream oss;
|
||||||
|
oss << R"(
|
||||||
# Critical Constraints
|
# Critical Constraints
|
||||||
|
|
||||||
1. **Output Format:** You MUST respond with ONLY a JSON object with the following structure:
|
1. **Output Format:** You MUST respond with ONLY a JSON object with the following structure:
|
||||||
@@ -262,17 +474,29 @@ std::string PromptBuilder::BuildConstraintsSection() {
|
|||||||
- Multiple tile placement: multiple overworld set-tile commands
|
- Multiple tile placement: multiple overworld set-tile commands
|
||||||
- Validation: single rom validate command
|
- Validation: single rom validate command
|
||||||
|
|
||||||
4. **Tile IDs Reference (ALTTP):**
|
4. **Error Prevention:**
|
||||||
- Tree: 0x02E
|
|
||||||
- House (2x2): 0x0C0, 0x0C1, 0x0D0, 0x0D1
|
|
||||||
- Water: 0x038
|
|
||||||
- Grass: 0x000
|
|
||||||
|
|
||||||
5. **Error Prevention:**
|
|
||||||
- Always export before modifying palettes
|
- Always export before modifying palettes
|
||||||
- Use temporary file names (temp_*.json) for intermediate files
|
- Use temporary file names (temp_*.json) for intermediate files
|
||||||
- Validate coordinates are within bounds
|
- Validate coordinates are within bounds
|
||||||
)";
|
)";
|
||||||
|
|
||||||
|
if (!tile_reference_.empty()) {
|
||||||
|
oss << "\n" << BuildTileReferenceSection();
|
||||||
|
}
|
||||||
|
|
||||||
|
return oss.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string PromptBuilder::BuildTileReferenceSection() const {
|
||||||
|
std::ostringstream oss;
|
||||||
|
oss << "# Tile16 Reference (ALTTP)\n\n";
|
||||||
|
|
||||||
|
for (const auto& [alias, value] : tile_reference_) {
|
||||||
|
oss << "- " << alias << ": " << value << "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
oss << "\n";
|
||||||
|
return oss.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string PromptBuilder::BuildContextSection(const RomContext& context) {
|
std::string PromptBuilder::BuildContextSection(const RomContext& context) {
|
||||||
@@ -322,8 +546,13 @@ std::string PromptBuilder::BuildSystemInstruction() {
|
|||||||
<< "the user's request.\n\n";
|
<< "the user's request.\n\n";
|
||||||
|
|
||||||
if (catalogue_loaded_) {
|
if (catalogue_loaded_) {
|
||||||
|
if (!command_docs_.empty()) {
|
||||||
oss << BuildCommandReference();
|
oss << BuildCommandReference();
|
||||||
}
|
}
|
||||||
|
if (!tool_specs_.empty()) {
|
||||||
|
oss << BuildToolReference();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
oss << BuildConstraintsSection();
|
oss << BuildConstraintsSection();
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
#ifndef YAZE_CLI_SERVICE_PROMPT_BUILDER_H_
|
#ifndef YAZE_CLI_SERVICE_PROMPT_BUILDER_H_
|
||||||
#define YAZE_CLI_SERVICE_PROMPT_BUILDER_H_
|
#define YAZE_CLI_SERVICE_PROMPT_BUILDER_H_
|
||||||
|
|
||||||
|
#include <map>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <map>
|
|
||||||
|
|
||||||
|
#include "absl/status/status.h"
|
||||||
#include "absl/status/statusor.h"
|
#include "absl/status/statusor.h"
|
||||||
|
#include "nlohmann/json_fwd.hpp"
|
||||||
#include "cli/service/ai/common.h"
|
#include "cli/service/ai/common.h"
|
||||||
#include "cli/service/resources/resource_context_builder.h"
|
#include "cli/service/resources/resource_context_builder.h"
|
||||||
#include "app/rom.h"
|
#include "app/rom.h"
|
||||||
@@ -26,6 +28,20 @@ struct FewShotExample {
|
|||||||
std::vector<ToolCall> tool_calls;
|
std::vector<ToolCall> tool_calls;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct ToolArgument {
|
||||||
|
std::string name;
|
||||||
|
std::string description;
|
||||||
|
bool required = false;
|
||||||
|
std::string example;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ToolSpecification {
|
||||||
|
std::string name;
|
||||||
|
std::string description;
|
||||||
|
std::vector<ToolArgument> arguments;
|
||||||
|
std::string usage_notes;
|
||||||
|
};
|
||||||
|
|
||||||
// ROM context information to inject into prompts
|
// ROM context information to inject into prompts
|
||||||
struct RomContext {
|
struct RomContext {
|
||||||
std::string rom_path;
|
std::string rom_path;
|
||||||
@@ -65,22 +81,34 @@ class PromptBuilder {
|
|||||||
// Get few-shot examples for specific category
|
// Get few-shot examples for specific category
|
||||||
std::vector<FewShotExample> GetExamplesForCategory(
|
std::vector<FewShotExample> GetExamplesForCategory(
|
||||||
const std::string& category);
|
const std::string& category);
|
||||||
|
std::string LookupTileId(const std::string& alias) const;
|
||||||
|
const std::map<std::string, std::string>& tile_reference() const {
|
||||||
|
return tile_reference_;
|
||||||
|
}
|
||||||
|
|
||||||
// Set verbosity level (0=minimal, 1=standard, 2=verbose)
|
// Set verbosity level (0=minimal, 1=standard, 2=verbose)
|
||||||
void SetVerbosity(int level) { verbosity_ = level; }
|
void SetVerbosity(int level) { verbosity_ = level; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::string BuildCommandReference();
|
std::string BuildCommandReference() const;
|
||||||
std::string BuildFewShotExamplesSection();
|
std::string BuildFewShotExamplesSection() const;
|
||||||
|
std::string BuildToolReference() const;
|
||||||
std::string BuildContextSection(const RomContext& context);
|
std::string BuildContextSection(const RomContext& context);
|
||||||
std::string BuildConstraintsSection();
|
std::string BuildConstraintsSection() const;
|
||||||
|
std::string BuildTileReferenceSection() const;
|
||||||
void LoadDefaultExamples();
|
absl::StatusOr<std::string> ResolveCataloguePath(const std::string& yaml_path) const;
|
||||||
|
void ClearCatalogData();
|
||||||
|
absl::Status ParseCommands(const nlohmann::json& commands);
|
||||||
|
absl::Status ParseTools(const nlohmann::json& tools);
|
||||||
|
absl::Status ParseExamples(const nlohmann::json& examples);
|
||||||
|
void ParseTileReference(const nlohmann::json& tile_reference);
|
||||||
|
|
||||||
Rom* rom_ = nullptr;
|
Rom* rom_ = nullptr;
|
||||||
std::unique_ptr<ResourceContextBuilder> resource_context_builder_;
|
std::unique_ptr<ResourceContextBuilder> resource_context_builder_;
|
||||||
std::map<std::string, std::string> command_docs_; // Command name -> docs
|
std::map<std::string, std::string> command_docs_; // Command name -> docs
|
||||||
std::vector<FewShotExample> examples_;
|
std::vector<FewShotExample> examples_;
|
||||||
|
std::vector<ToolSpecification> tool_specs_;
|
||||||
|
std::map<std::string, std::string> tile_reference_;
|
||||||
int verbosity_ = 1;
|
int verbosity_ = 1;
|
||||||
bool catalogue_loaded_ = false;
|
bool catalogue_loaded_ = false;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -11,6 +11,39 @@ if(NOT ftxui_POPULATED)
|
|||||||
add_subdirectory(${ftxui_SOURCE_DIR} ${ftxui_BINARY_DIR} EXCLUDE_FROM_ALL)
|
add_subdirectory(${ftxui_SOURCE_DIR} ${ftxui_BINARY_DIR} EXCLUDE_FROM_ALL)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
find_package(yaml-cpp CONFIG)
|
||||||
|
if(NOT yaml-cpp_FOUND)
|
||||||
|
message(STATUS "yaml-cpp not found via package config, fetching from source")
|
||||||
|
FetchContent_Declare(yaml-cpp
|
||||||
|
GIT_REPOSITORY https://github.com/jbeder/yaml-cpp.git
|
||||||
|
GIT_TAG 0.8.0
|
||||||
|
)
|
||||||
|
FetchContent_GetProperties(yaml-cpp)
|
||||||
|
if(NOT yaml-cpp_POPULATED)
|
||||||
|
FetchContent_Populate(yaml-cpp)
|
||||||
|
|
||||||
|
# Ensure compatibility with newer CMake versions by adjusting minimum requirement
|
||||||
|
set(_yaml_cpp_cmakelists "${yaml-cpp_SOURCE_DIR}/CMakeLists.txt")
|
||||||
|
if(EXISTS "${_yaml_cpp_cmakelists}")
|
||||||
|
file(READ "${_yaml_cpp_cmakelists}" _yaml_cpp_cmake_contents)
|
||||||
|
if(_yaml_cpp_cmake_contents MATCHES "cmake_minimum_required\\(VERSION 3\\.4\\)")
|
||||||
|
string(REPLACE "cmake_minimum_required(VERSION 3.4)"
|
||||||
|
"cmake_minimum_required(VERSION 3.5)"
|
||||||
|
_yaml_cpp_cmake_contents "${_yaml_cpp_cmake_contents}")
|
||||||
|
file(WRITE "${_yaml_cpp_cmakelists}" "${_yaml_cpp_cmake_contents}")
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set(YAML_CPP_BUILD_TESTS OFF CACHE BOOL "Disable yaml-cpp tests" FORCE)
|
||||||
|
set(YAML_CPP_BUILD_CONTRIB OFF CACHE BOOL "Disable yaml-cpp contrib" FORCE)
|
||||||
|
set(YAML_CPP_BUILD_TOOLS OFF CACHE BOOL "Disable yaml-cpp tools" FORCE)
|
||||||
|
set(YAML_CPP_INSTALL OFF CACHE BOOL "Disable yaml-cpp install" FORCE)
|
||||||
|
set(YAML_CPP_FORMAT_SOURCE OFF CACHE BOOL "Disable yaml-cpp format target" FORCE)
|
||||||
|
|
||||||
|
add_subdirectory(${yaml-cpp_SOURCE_DIR} ${yaml-cpp_BINARY_DIR} EXCLUDE_FROM_ALL)
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
# Platform-specific file dialog sources
|
# Platform-specific file dialog sources
|
||||||
if(APPLE)
|
if(APPLE)
|
||||||
set(FILE_DIALOG_SRC
|
set(FILE_DIALOG_SRC
|
||||||
@@ -128,6 +161,7 @@ target_link_libraries(
|
|||||||
z3ed PUBLIC
|
z3ed PUBLIC
|
||||||
asar-static
|
asar-static
|
||||||
yaze_agent
|
yaze_agent
|
||||||
|
yaml-cpp
|
||||||
ftxui::component
|
ftxui::component
|
||||||
ftxui::screen
|
ftxui::screen
|
||||||
ftxui::dom
|
ftxui::dom
|
||||||
|
|||||||
@@ -7,7 +7,8 @@
|
|||||||
"name": "sdl2",
|
"name": "sdl2",
|
||||||
"platform": "!uwp",
|
"platform": "!uwp",
|
||||||
"features": ["vulkan"]
|
"features": ["vulkan"]
|
||||||
}
|
},
|
||||||
|
"yaml-cpp"
|
||||||
],
|
],
|
||||||
"builtin-baseline": "4bee3f5aae7aefbc129ca81c33d6a062b02fcf3b",
|
"builtin-baseline": "4bee3f5aae7aefbc129ca81c33d6a062b02fcf3b",
|
||||||
"overrides": [],
|
"overrides": [],
|
||||||
|
|||||||
Reference in New Issue
Block a user