yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
prompt_builder.cc
Go to the documentation of this file.
2
3#include <cstdlib>
4#include <filesystem>
5#include <fstream>
6#include <iostream>
7#include <sstream>
8
9#include "absl/strings/ascii.h"
10#include "absl/strings/str_cat.h"
11#include "absl/strings/str_join.h"
13#include "nlohmann/json.hpp"
14#include "util/platform_paths.h"
15
16// yaml-cpp is optional - only include if available
17#ifdef YAZE_HAS_YAML_CPP
18#include "yaml-cpp/yaml.h"
19#endif
20
21namespace yaze {
22namespace cli {
23
24namespace {
25
26#ifdef YAZE_HAS_YAML_CPP
27bool IsYamlBool(const std::string& value) {
28 const std::string lower = absl::AsciiStrToLower(value);
29 return lower == "true" || lower == "false" || lower == "yes" ||
30 lower == "no" || lower == "on" || lower == "off";
31}
32
33nlohmann::json YamlToJson(const YAML::Node& node) {
34 if (!node) {
35 return nlohmann::json();
36 }
37
38 switch (node.Type()) {
39 case YAML::NodeType::Scalar: {
40 const std::string scalar = node.as<std::string>("");
41
42 if (IsYamlBool(scalar)) {
43 return node.as<bool>();
44 }
45
46 if (scalar == "~" || absl::AsciiStrToLower(scalar) == "null") {
47 return nlohmann::json();
48 }
49
50 return scalar;
51 }
52 case YAML::NodeType::Sequence: {
53 nlohmann::json array = nlohmann::json::array();
54 for (const auto& item : node) {
55 array.push_back(YamlToJson(item));
56 }
57 return array;
58 }
59 case YAML::NodeType::Map: {
60 nlohmann::json object = nlohmann::json::object();
61 for (const auto& kv : node) {
62 object[kv.first.as<std::string>()] = YamlToJson(kv.second);
63 }
64 return object;
65 }
66 default:
67 return nlohmann::json();
68 }
69}
70#endif // YAZE_HAS_YAML_CPP
71
72} // namespace
73
75
77 command_docs_.clear();
78 examples_.clear();
79 tool_specs_.clear();
80 tile_reference_.clear();
81 catalogue_loaded_ = false;
82}
83
84absl::StatusOr<std::string> PromptBuilder::ResolveCataloguePath(
85 const std::string& yaml_path) const {
86 // If an explicit path is provided, check it first
87 if (!yaml_path.empty()) {
88 std::error_code ec;
89 if (std::filesystem::exists(yaml_path, ec) && !ec) {
90 return yaml_path;
91 }
92 }
93
94 // Check environment variable override
95 if (const char* env_path = std::getenv("YAZE_AGENT_CATALOGUE")) {
96 if (*env_path != '\0') {
97 std::error_code ec;
98 if (std::filesystem::exists(env_path, ec) && !ec) {
99 return std::string(env_path);
100 }
101 }
102 }
103
104 // Use PlatformPaths to find the asset in standard locations
105 // Try the requested path (default is prompt_catalogue.yaml)
106 std::string relative_path =
107 yaml_path.empty() ? "agent/prompt_catalogue.yaml" : yaml_path;
108
109 auto result = util::PlatformPaths::FindAsset(relative_path);
110 if (result.ok()) {
111 return result->string();
112 }
113
114 return result.status();
115}
116
118 const std::string& yaml_path) {
119#if !defined(YAZE_WITH_JSON) || !defined(YAZE_HAS_YAML_CPP)
120 // Gracefully degrade if JSON or yaml-cpp support not available
121 (void)yaml_path; // Suppress unused parameter warning
122 std::cerr
123 << "⚠️ PromptBuilder requires JSON and yaml-cpp support for catalogue loading\n"
124 << " Build with -DYAZE_WITH_JSON=ON (mac-ai preset already does) and install yaml-cpp\n"
125 << " AI features will use basic prompts without tool definitions\n";
126 return absl::OkStatus(); // Don't fail, just skip catalogue loading
127#else
128 auto resolved_or = ResolveCataloguePath(yaml_path);
129 if (!resolved_or.ok()) {
131 return resolved_or.status();
132 }
133
134 const std::string& resolved_path = resolved_or.value();
135
136 YAML::Node root;
137 try {
138 root = YAML::LoadFile(resolved_path);
139 } catch (const YAML::BadFile& e) {
141 return absl::NotFoundError(absl::StrCat(
142 "Unable to open prompt catalogue at ", resolved_path, ": ", e.what()));
143 } catch (const YAML::ParserException& e) {
145 return absl::InvalidArgumentError(absl::StrCat(
146 "Failed to parse prompt catalogue at ", resolved_path, ": ", e.what()));
147 }
148
149 nlohmann::json catalogue = YamlToJson(root);
151
152 if (catalogue.contains("commands")) {
153 if (auto status = ParseCommands(catalogue["commands"]); !status.ok()) {
154 return status;
155 }
156 }
157
158 if (catalogue.contains("tools")) {
159 if (auto status = ParseTools(catalogue["tools"]); !status.ok()) {
160 return status;
161 }
162 }
163
164 if (catalogue.contains("examples")) {
165 if (auto status = ParseExamples(catalogue["examples"]); !status.ok()) {
166 return status;
167 }
168 }
169
170 if (catalogue.contains("tile16_reference")) {
171 ParseTileReference(catalogue["tile16_reference"]);
172 }
173
174 catalogue_loaded_ = true;
175 return absl::OkStatus();
176#endif // YAZE_WITH_JSON
177}
178
179absl::Status PromptBuilder::ParseCommands(const nlohmann::json& commands) {
180 if (!commands.is_object()) {
181 return absl::InvalidArgumentError(
182 "commands section must be an object mapping command names to docs");
183 }
184
185 for (const auto& [name, value] : commands.items()) {
186 if (!value.is_string()) {
187 return absl::InvalidArgumentError(
188 absl::StrCat("Command entry for ", name, " must be a string"));
189 }
190 command_docs_[name] = value.get<std::string>();
191 }
192
193 return absl::OkStatus();
194}
195
196absl::Status PromptBuilder::ParseTools(const nlohmann::json& tools) {
197 if (!tools.is_array()) {
198 return absl::InvalidArgumentError("tools section must be an array");
199 }
200
201 for (const auto& tool_json : tools) {
202 if (!tool_json.is_object()) {
203 return absl::InvalidArgumentError(
204 "Each tool entry must be an object with name/description");
205 }
206
208 if (tool_json.contains("name") && tool_json["name"].is_string()) {
209 spec.name = tool_json["name"].get<std::string>();
210 } else {
211 return absl::InvalidArgumentError("Tool entry missing name");
212 }
213
214 if (tool_json.contains("description") &&
215 tool_json["description"].is_string()) {
216 spec.description = tool_json["description"].get<std::string>();
217 }
218
219 if (tool_json.contains("usage_notes") &&
220 tool_json["usage_notes"].is_string()) {
221 spec.usage_notes = tool_json["usage_notes"].get<std::string>();
222 }
223
224 if (tool_json.contains("arguments")) {
225 const auto& args = tool_json["arguments"];
226 if (!args.is_array()) {
227 return absl::InvalidArgumentError(absl::StrCat(
228 "Tool arguments for ", spec.name, " must be an array"));
229 }
230 for (const auto& arg_json : args) {
231 if (!arg_json.is_object()) {
232 return absl::InvalidArgumentError(absl::StrCat(
233 "Argument entries for ", spec.name, " must be objects"));
234 }
235 ToolArgument arg;
236 if (arg_json.contains("name") && arg_json["name"].is_string()) {
237 arg.name = arg_json["name"].get<std::string>();
238 } else {
239 return absl::InvalidArgumentError(absl::StrCat(
240 "Argument entry for ", spec.name, " is missing a name"));
241 }
242 if (arg_json.contains("description") &&
243 arg_json["description"].is_string()) {
244 arg.description = arg_json["description"].get<std::string>();
245 }
246 if (arg_json.contains("required")) {
247 if (!arg_json["required"].is_boolean()) {
248 return absl::InvalidArgumentError(
249 absl::StrCat("Argument 'required' flag for ", spec.name,
250 "::", arg.name, " must be boolean"));
251 }
252 arg.required = arg_json["required"].get<bool>();
253 }
254 if (arg_json.contains("example") && arg_json["example"].is_string()) {
255 arg.example = arg_json["example"].get<std::string>();
256 }
257 spec.arguments.push_back(std::move(arg));
258 }
259 }
260
261 tool_specs_.push_back(std::move(spec));
262 }
263
264 return absl::OkStatus();
265}
266
267absl::Status PromptBuilder::ParseExamples(const nlohmann::json& examples) {
268 if (!examples.is_array()) {
269 return absl::InvalidArgumentError("examples section must be an array");
270 }
271
272 for (const auto& example_json : examples) {
273 if (!example_json.is_object()) {
274 return absl::InvalidArgumentError("Each example entry must be an object");
275 }
276
277 FewShotExample example;
278 if (example_json.contains("user_prompt") &&
279 example_json["user_prompt"].is_string()) {
280 example.user_prompt = example_json["user_prompt"].get<std::string>();
281 } else {
282 return absl::InvalidArgumentError("Example missing user_prompt");
283 }
284
285 if (example_json.contains("text_response") &&
286 example_json["text_response"].is_string()) {
287 example.text_response = example_json["text_response"].get<std::string>();
288 }
289
290 if (example_json.contains("reasoning") &&
291 example_json["reasoning"].is_string()) {
292 example.explanation = example_json["reasoning"].get<std::string>();
293 }
294
295 if (example_json.contains("commands")) {
296 const auto& commands = example_json["commands"];
297 if (!commands.is_array()) {
298 return absl::InvalidArgumentError(absl::StrCat(
299 "Example commands for ", example.user_prompt, " must be an array"));
300 }
301 for (const auto& cmd : commands) {
302 if (!cmd.is_string()) {
303 return absl::InvalidArgumentError(absl::StrCat(
304 "Command entries for ", example.user_prompt, " must be strings"));
305 }
306 example.expected_commands.push_back(cmd.get<std::string>());
307 }
308 }
309
310 if (example_json.contains("tool_calls")) {
311 const auto& calls = example_json["tool_calls"];
312 if (!calls.is_array()) {
313 return absl::InvalidArgumentError(absl::StrCat(
314 "Tool calls for ", example.user_prompt, " must be an array"));
315 }
316 for (const auto& call_json : calls) {
317 if (!call_json.is_object()) {
318 return absl::InvalidArgumentError(
319 absl::StrCat("Tool call entries for ", example.user_prompt,
320 " must be objects"));
321 }
322 ToolCall call;
323 if (call_json.contains("tool_name") &&
324 call_json["tool_name"].is_string()) {
325 call.tool_name = call_json["tool_name"].get<std::string>();
326 } else {
327 return absl::InvalidArgumentError(absl::StrCat(
328 "Tool call missing tool_name in example: ", example.user_prompt));
329 }
330 if (call_json.contains("args")) {
331 const auto& args = call_json["args"];
332 if (!args.is_object()) {
333 return absl::InvalidArgumentError(
334 absl::StrCat("Tool call args for ", example.user_prompt,
335 " must be an object"));
336 }
337 for (const auto& [key, value] : args.items()) {
338 if (!value.is_string()) {
339 return absl::InvalidArgumentError(
340 absl::StrCat("Tool call arg value for ", example.user_prompt,
341 " must be a string"));
342 }
343 call.args[key] = value.get<std::string>();
344 }
345 }
346 example.tool_calls.push_back(std::move(call));
347 }
348 }
349
350 example.explanation =
351 example_json.value("explanation", example.explanation);
352 examples_.push_back(std::move(example));
353 }
354
355 return absl::OkStatus();
356}
357
358void PromptBuilder::ParseTileReference(const nlohmann::json& tile_reference) {
359 if (!tile_reference.is_object()) {
360 return;
361 }
362
363 for (const auto& [alias, value] : tile_reference.items()) {
364 if (value.is_string()) {
365 tile_reference_[alias] = value.get<std::string>();
366 }
367 }
368}
369
370std::string PromptBuilder::LookupTileId(const std::string& alias) const {
371 auto it = tile_reference_.find(alias);
372 if (it != tile_reference_.end()) {
373 return it->second;
374 }
375 return "";
376}
377
379 std::ostringstream oss;
380
381 oss << "# Available z3ed Commands\n\n";
382
383 for (const auto& [cmd, docs] : command_docs_) {
384 oss << "## " << cmd << "\n";
385 oss << docs << "\n\n";
386 }
387
388 return oss.str();
389}
390
392 if (tool_specs_.empty()) {
393 return "";
394 }
395
396 std::ostringstream oss;
397 oss << "# Available Agent Tools\n\n";
398
399 for (const auto& spec : tool_specs_) {
400 oss << "## " << spec.name << "\n";
401 if (!spec.description.empty()) {
402 oss << spec.description << "\n\n";
403 }
404
405 if (!spec.arguments.empty()) {
406 oss << "| Argument | Required | Description | Example |\n";
407 oss << "| --- | --- | --- | --- |\n";
408 for (const auto& arg : spec.arguments) {
409 oss << "| `" << arg.name << "` | " << (arg.required ? "yes" : "no")
410 << " | " << arg.description << " | "
411 << (arg.example.empty() ? "" : "`" + arg.example + "`") << " |\n";
412 }
413 oss << "\n";
414 }
415
416 if (!spec.usage_notes.empty()) {
417 oss << "_Usage:_ " << spec.usage_notes << "\n\n";
418 }
419 }
420
421 return oss.str();
422}
423
425 if (tool_specs_.empty()) {
426 return "[]";
427 }
428
429 nlohmann::json tools_array = nlohmann::json::array();
430
431 for (const auto& spec : tool_specs_) {
432 nlohmann::json tool;
433 tool["type"] = "function";
434
435 nlohmann::json function;
436 function["name"] = spec.name;
437 function["description"] = spec.description;
438 if (!spec.usage_notes.empty()) {
439 function["description"] = spec.description + " " + spec.usage_notes;
440 }
441
442 nlohmann::json parameters;
443 parameters["type"] = "object";
444
445 nlohmann::json properties = nlohmann::json::object();
446 nlohmann::json required = nlohmann::json::array();
447
448 for (const auto& arg : spec.arguments) {
449 nlohmann::json arg_schema;
450 arg_schema["type"] = "string"; // All CLI args are strings
451 arg_schema["description"] = arg.description;
452 if (!arg.example.empty()) {
453 arg_schema["example"] = arg.example;
454 }
455 properties[arg.name] = arg_schema;
456
457 if (arg.required) {
458 required.push_back(arg.name);
459 }
460 }
461
462 parameters["properties"] = properties;
463 if (!required.empty()) {
464 parameters["required"] = required;
465 }
466
467 function["parameters"] = parameters;
468 tool["function"] = function;
469 tools_array.push_back(tool);
470 }
471
472 return tools_array.dump(2);
473}
474
476 std::ostringstream oss;
477
478 oss << "# Example Command Sequences\n\n";
479 oss << "Here are proven examples of how to accomplish common tasks:\n\n";
480
481 for (const auto& example : examples_) {
482 oss << "**User Request:** \"" << example.user_prompt << "\"\n";
483 oss << "**Structured Response:**\n";
484
485 nlohmann::json example_json = nlohmann::json::object();
486 if (!example.text_response.empty()) {
487 example_json["text_response"] = example.text_response;
488 }
489 if (!example.expected_commands.empty()) {
490 example_json["commands"] = example.expected_commands;
491 }
492 if (!example.explanation.empty()) {
493 example_json["reasoning"] = example.explanation;
494 }
495 if (!example.tool_calls.empty()) {
496 nlohmann::json calls = nlohmann::json::array();
497 for (const auto& call : example.tool_calls) {
498 nlohmann::json call_json;
499 call_json["tool_name"] = call.tool_name;
500 nlohmann::json args = nlohmann::json::object();
501 for (const auto& [key, value] : call.args) {
502 args[key] = value;
503 }
504 call_json["args"] = std::move(args);
505 calls.push_back(std::move(call_json));
506 }
507 example_json["tool_calls"] = std::move(calls);
508 }
509
510 oss << "```json\n" << example_json.dump(2) << "\n```\n\n";
511 }
512
513 return oss.str();
514}
515
517 // Try to load from file first using FindAsset
518 auto file_path =
519 util::PlatformPaths::FindAsset("agent/tool_calling_instructions.txt");
520 if (file_path.ok()) {
521 std::ifstream file(file_path->string());
522 if (file.is_open()) {
523 std::string content((std::istreambuf_iterator<char>(file)),
524 std::istreambuf_iterator<char>());
525 if (!content.empty()) {
526 std::ostringstream oss;
527 oss << content;
528
529 // Add tool schemas if available
530 if (!tool_specs_.empty()) {
531 oss << "\n\n# Available Tools for ROM Inspection\n\n";
532 oss << "You have access to the following tools to answer "
533 "questions:\n\n";
534 oss << "```json\n";
536 oss << "\n```\n\n";
537 oss << "**Tool Call Example (Initial Request):**\n";
538 oss << "```json\n";
539 oss << R"({
540 "tool_calls": [
541 {
542 "tool_name": "resource-list",
543 "args": {
544 "type": "dungeon"
545 }
546 }
547 ],
548 "reasoning": "I need to call the resource-list tool to get the dungeon information."
549})";
550 oss << "\n```\n\n";
551 oss << "**Tool Result Response (After Tool Executes):**\n";
552 oss << "```json\n";
553 oss << R"({
554 "text_response": "I found the following dungeons in the ROM: Hyrule Castle, Eastern Palace, Desert Palace, Tower of Hera, Palace of Darkness, Swamp Palace, Skull Woods, Thieves' Town, Ice Palace, Misery Mire, Turtle Rock, and Ganon's Tower.",
555 "reasoning": "The tool returned a list of 12 dungeons which I've formatted into a readable response."
556})";
557 oss << "\n```\n";
558 }
559
560 if (!tile_reference_.empty()) {
561 oss << "\n" << BuildTileReferenceSection();
562 }
563
564 return oss.str();
565 }
566 }
567 }
568
569 // Fallback to embedded version if file not found
570 std::ostringstream oss;
571 oss << R"(
572# Critical Constraints
573
5741. **Output Format:** You MUST respond with ONLY a JSON object with the following structure:
575 {
576 "text_response": "Your natural language reply to the user.",
577 "tool_calls": [{ "tool_name": "tool_name", "args": { "arg1": "value1" } }],
578 "commands": ["command1", "command2"],
579 "reasoning": "Your thought process."
580 }
581 - `text_response` is for conversational replies.
582 - `tool_calls` is for asking questions about the ROM. Use the available tools listed below.
583 - `commands` is for generating commands to modify the ROM.
584 - All fields are optional, but you should always provide at least one.
585
5862. **Tool Calling Workflow (CRITICAL):**
587 WHEN YOU CALL A TOOL:
588 a) First response: Include tool_calls with the tool name and arguments
589 b) The tool will execute and you'll receive results in the next message
590 c) Second response: You MUST provide a text_response that answers the user's question using the tool results
591 d) DO NOT call the same tool again unless you need different parameters
592 e) DO NOT leave text_response empty after receiving tool results
593
594 Example conversation flow:
595 User: "What dungeons are in this ROM?"
596 You (first): {"tool_calls": [{"tool_name": "resource-list", "args": {"type": "dungeon"}}]}
597 [Tool executes and returns: {"dungeons": ["Hyrule Castle", "Eastern Palace", ...]}]
598 You (second): {"text_response": "Based on the ROM data, there are 12 dungeons including Hyrule Castle, Eastern Palace, Desert Palace, Tower of Hera, and more."}
599
6003. **Tool Usage:** When the user asks a question about the ROM state, use tool_calls instead of commands
601 - Tools are read-only and return information
602 - Commands modify the ROM and should only be used when explicitly requested
603 - You can call multiple tools in one response
604 - Always use JSON format for tool results
605 - ALWAYS provide text_response after receiving tool results
606
6074. **Command Syntax:** Follow the exact syntax shown in examples
608 - Use correct flag names (--group, --id, --to, --from, etc.)
609 - Use hex format for colors (0xRRGGBB) and tile IDs (0xNNN)
610 - Coordinates are 0-based indices
611
6125. **Common Patterns:**
613 - Palette modifications: export → set-color → import
614 - Multiple tile placement: multiple overworld set-tile commands
615 - Validation: single rom validate command
616
6176. **Error Prevention:**
618 - Always export before modifying palettes
619 - Use temporary file names (temp_*.json) for intermediate files
620 - Validate coordinates are within bounds
621)";
622
623 if (!tool_specs_.empty()) {
624 oss << "\n# Available Tools for ROM Inspection\n\n";
625 oss << "You have access to the following tools to answer questions:\n\n";
626 oss << "```json\n";
628 oss << "\n```\n\n";
629 oss << "**Tool Call Example (Initial Request):**\n";
630 oss << "```json\n";
631 oss << R"({
632 "tool_calls": [
633 {
634 "tool_name": "resource-list",
635 "args": {
636 "type": "dungeon"
637 }
638 }
639 ],
640 "reasoning": "I need to call the resource-list tool to get the dungeon information."
641})";
642 oss << "\n```\n\n";
643 oss << "**Tool Result Response (After Tool Executes):**\n";
644 oss << "```json\n";
645 oss << R"({
646 "text_response": "I found the following dungeons in the ROM: Hyrule Castle, Eastern Palace, Desert Palace, Tower of Hera, Palace of Darkness, Swamp Palace, Skull Woods, Thieves' Town, Ice Palace, Misery Mire, Turtle Rock, and Ganon's Tower.",
647 "reasoning": "The tool returned a list of 12 dungeons which I've formatted into a readable response."
648})";
649 oss << "\n```\n";
650 }
651
652 if (!tile_reference_.empty()) {
653 oss << "\n" << BuildTileReferenceSection();
654 }
655
656 return oss.str();
657}
658
660 std::ostringstream oss;
661 oss << "# Tile16 Reference (ALTTP)\n\n";
662
663 for (const auto& [alias, value] : tile_reference_) {
664 oss << "- " << alias << ": " << value << "\n";
665 }
666
667 oss << "\n";
668 return oss.str();
669}
670
671std::string PromptBuilder::BuildContextSection(const RomContext& context) {
672 std::ostringstream oss;
673
674 oss << "# Current ROM Context\n\n";
675
676 // Use ResourceContextBuilder if a ROM is available
677 if (rom_ && rom_->is_loaded()) {
680 std::make_unique<ResourceContextBuilder>(rom_);
681 }
682 auto resource_context_or =
683 resource_context_builder_->BuildResourceContext();
684 if (resource_context_or.ok()) {
685 oss << resource_context_or.value();
686 }
687 }
688
689 if (context.rom_loaded) {
690 oss << "- **ROM Loaded:** Yes (" << context.rom_path << ")\n";
691 } else {
692 oss << "- **ROM Loaded:** No\n";
693 }
694
695 if (!context.current_editor.empty()) {
696 oss << "- **Active Editor:** " << context.current_editor << "\n";
697 }
698
699 if (!context.editor_state.empty()) {
700 oss << "- **Editor State:**\n";
701 for (const auto& [key, value] : context.editor_state) {
702 oss << " - " << key << ": " << value << "\n";
703 }
704 }
705
706 oss << "\n";
707 return oss.str();
708}
709
711 // Try to load from file first using FindAsset
712 auto file_path = util::PlatformPaths::FindAsset("agent/system_prompt.txt");
713 if (file_path.ok()) {
714 std::ifstream file(file_path->string());
715 if (file.is_open()) {
716 std::string content((std::istreambuf_iterator<char>(file)),
717 std::istreambuf_iterator<char>());
718 if (!content.empty()) {
719 std::ostringstream oss;
720 oss << content;
721
722 // Add command reference if available
723 if (catalogue_loaded_ && !command_docs_.empty()) {
724 oss << "\n\n" << BuildCommandReference();
725 }
726
727 // Add tool reference if available
728 if (!tool_specs_.empty()) {
729 oss << "\n\n" << BuildToolReference();
730 }
731
732 return oss.str();
734 }
735 }
736
737 // Fallback to embedded version if file not found
738 std::ostringstream oss;
739
740 oss << "You are an expert ROM hacking assistant for The Legend of Zelda: "
741 << "A Link to the Past (ALTTP).\n\n";
742
743 oss << "Your task is to generate a sequence of z3ed CLI commands to achieve "
744 << "the user's request.\n\n";
745
746 if (catalogue_loaded_) {
747 if (!command_docs_.empty()) {
748 oss << BuildCommandReference();
749 }
750 if (!tool_specs_.empty()) {
751 oss << BuildToolReference();
752 }
753 }
754
756
757 oss << "\n**Response Format:**\n";
758 oss << "```json\n";
759 oss << "[\"command1 --flag value\", \"command2 --flag value\"]\n";
760 oss << "```\n";
761
762 return oss.str();
763}
764
766 std::ostringstream oss;
767
768 oss << BuildSystemInstruction();
769 oss << "\n---\n\n";
771
772 return oss.str();
773}
774
775std::string PromptBuilder::BuildContextualPrompt(const std::string& user_prompt,
776 const RomContext& context) {
777 std::ostringstream oss;
778
779 if (context.rom_loaded || !context.current_editor.empty()) {
780 oss << BuildContextSection(context);
781 oss << "---\n\n";
782 }
783
784 oss << "**User Request:** " << user_prompt << "\n\n";
785 oss << "Generate the appropriate z3ed commands as a JSON array.";
786
787 return oss.str();
788}
789
791 const std::vector<agent::ChatMessage>& history) {
792 std::ostringstream oss;
793 oss << "This is a conversation between a user and an expert ROM hacking "
794 "assistant.\n\n";
795
796 for (const auto& msg : history) {
797 if (msg.sender == agent::ChatMessage::Sender::kUser) {
798 oss << "User: " << msg.message << "\n";
799 } else {
800 oss << "Agent: " << msg.message << "\n";
801 }
802 }
803 oss << "\nBased on this conversation, provide a response in the required "
804 "JSON "
805 "format.";
806 return oss.str();
807}
808
809void PromptBuilder::AddFewShotExample(const FewShotExample& example) {
810 examples_.push_back(example);
811}
812
813std::vector<FewShotExample> PromptBuilder::GetExamplesForCategory(
814 const std::string& category) {
815 std::vector<FewShotExample> result;
816
817 for (const auto& example : examples_) {
818 // Simple category matching based on keywords
819 if (category == "palette" &&
820 (example.user_prompt.find("palette") != std::string::npos ||
821 example.user_prompt.find("color") != std::string::npos)) {
822 result.push_back(example);
823 } else if (category == "overworld" &&
824 (example.user_prompt.find("place") != std::string::npos ||
825 example.user_prompt.find("tree") != std::string::npos ||
826 example.user_prompt.find("house") != std::string::npos)) {
827 result.push_back(example);
828 } else if (category == "validation" &&
829 example.user_prompt.find("validate") != std::string::npos) {
830 result.push_back(example);
831 }
832 }
833
834 return result;
835}
836
837} // namespace cli
838} // namespace yaze
bool is_loaded() const
Definition rom.h:128
std::string BuildContextualPrompt(const std::string &user_prompt, const RomContext &context)
std::vector< FewShotExample > examples_
std::map< std::string, std::string > command_docs_
absl::Status ParseTools(const nlohmann::json &tools)
std::map< std::string, std::string > tile_reference_
std::unique_ptr< ResourceContextBuilder > resource_context_builder_
const std::map< std::string, std::string > & tile_reference() const
std::string BuildConstraintsSection() const
std::string BuildTileReferenceSection() const
void AddFewShotExample(const FewShotExample &example)
std::string BuildFunctionCallSchemas() const
std::string BuildSystemInstructionWithExamples()
std::string BuildPromptFromHistory(const std::vector< agent::ChatMessage > &history)
std::string BuildToolReference() const
std::string BuildContextSection(const RomContext &context)
void ParseTileReference(const nlohmann::json &tile_reference)
std::string BuildSystemInstruction()
std::string LookupTileId(const std::string &alias) const
absl::Status ParseExamples(const nlohmann::json &examples)
std::string BuildFewShotExamplesSection() const
std::vector< ToolSpecification > tool_specs_
std::string BuildCommandReference() const
absl::StatusOr< std::string > ResolveCataloguePath(const std::string &yaml_path) const
absl::Status LoadResourceCatalogue(const std::string &yaml_path)
absl::Status ParseCommands(const nlohmann::json &commands)
std::vector< FewShotExample > GetExamplesForCategory(const std::string &category)
static absl::StatusOr< std::filesystem::path > FindAsset(const std::string &relative_path)
Find an asset file in multiple standard locations.
std::vector< ToolCall > tool_calls
std::vector< std::string > expected_commands
std::map< std::string, std::string > args
Definition common.h:14
std::string tool_name
Definition common.h:13
std::vector< ToolArgument > arguments