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