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