yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
ai_service.cc
Go to the documentation of this file.
3
4#include <algorithm>
5#include <cctype>
6#include <sstream>
7
8#include "absl/strings/ascii.h"
9#include "absl/strings/match.h"
10#include "absl/strings/numbers.h"
11#include "absl/strings/str_format.h"
12
13namespace yaze {
14namespace cli {
15
16namespace {
17
18std::string ExtractRoomId(const std::string& normalized_prompt) {
19 size_t hex_pos = normalized_prompt.find("0x");
20 if (hex_pos != std::string::npos) {
21 std::string hex_value;
22 for (size_t i = hex_pos; i < normalized_prompt.size(); ++i) {
23 char c = normalized_prompt[i];
24 if (std::isxdigit(static_cast<unsigned char>(c)) || c == 'x') {
25 hex_value.push_back(c);
26 } else {
27 break;
28 }
29 }
30 if (hex_value.size() > 2) {
31 return hex_value;
32 }
33 }
34
35 // Fallback: look for decimal digits, then convert to hex string.
36 std::string digits;
37 for (char c : normalized_prompt) {
38 if (std::isdigit(static_cast<unsigned char>(c))) {
39 digits.push_back(c);
40 } else if (!digits.empty()) {
41 break;
42 }
43 }
44
45 if (!digits.empty()) {
46 int value = 0;
47 if (absl::SimpleAtoi(digits, &value)) {
48 return absl::StrFormat("0x%03X", value);
49 }
50 }
51
52 return "0x000";
53}
54
55std::string ExtractKeyword(const std::string& normalized_prompt) {
56 static const char* kStopwords[] = {
57 "search", "for", "resource", "resources", "label", "labels",
58 "please", "the", "a", "an", "list", "of", "in", "find"};
59
60 auto is_stopword = [](const std::string& word) {
61 for (const char* stop : kStopwords) {
62 if (word == stop) {
63 return true;
64 }
65 }
66 return false;
67 };
68
69 std::istringstream stream(normalized_prompt);
70 std::string token;
71 while (stream >> token) {
72 token.erase(std::remove_if(token.begin(), token.end(), [](unsigned char c) {
73 return !std::isalnum(c) && c != '_' && c != '-';
74 }),
75 token.end());
76 if (token.empty()) {
77 continue;
78 }
79 if (!is_stopword(token)) {
80 return token;
81 }
82 }
83
84 return "all";
85}
86
87} // namespace
88
89absl::StatusOr<AgentResponse> MockAIService::GenerateResponse(
90 const std::string& prompt) {
91 AgentResponse response;
92 const std::string normalized = absl::AsciiStrToLower(prompt);
93
94 if (normalized.empty()) {
95 response.text_response =
96 "Let's start with a prompt about the overworld or dungeons.";
97 return response;
98 }
99
100 if (absl::StrContains(normalized, "place") &&
101 absl::StrContains(normalized, "tree")) {
102 response.text_response =
103 "Sure, I can do that. Here's the command to place a tree.";
104 response.commands.push_back("overworld set-tile --map 0 --x 10 --y 20 --tile 0x02E");
105 response.reasoning =
106 "The user asked to place a tree tile16, so I generated the matching set-tile command.";
107 return response;
108 }
109
110 if (absl::StrContains(normalized, "list") &&
111 absl::StrContains(normalized, "resource")) {
112 std::string resource_type = "dungeon";
113 if (absl::StrContains(normalized, "overworld")) {
114 resource_type = "overworld";
115 } else if (absl::StrContains(normalized, "sprite")) {
116 resource_type = "sprite";
117 } else if (absl::StrContains(normalized, "palette")) {
118 resource_type = "palette";
119 }
120
121 ToolCall call;
122 call.tool_name = "resource-list";
123 call.args.emplace("type", resource_type);
124 response.text_response =
125 absl::StrFormat("Fetching %s labels from the ROM...", resource_type);
126 response.reasoning =
127 "Using the resource-list tool keeps the LLM in sync with project labels.";
128 response.tool_calls.push_back(call);
129 return response;
130 }
131
132 if (absl::StrContains(normalized, "search") &&
133 (absl::StrContains(normalized, "resource") ||
134 absl::StrContains(normalized, "label"))) {
135 ToolCall call;
136 call.tool_name = "resource-search";
137 call.args.emplace("query", ExtractKeyword(normalized));
138 response.text_response =
139 "Let me look through the labelled resources for matches.";
140 response.reasoning =
141 "Resource search provides fuzzy matching against the ROM label catalogue.";
142 response.tool_calls.push_back(call);
143 return response;
144 }
145
146 if (absl::StrContains(normalized, "sprite") &&
147 absl::StrContains(normalized, "room")) {
148 ToolCall call;
149 call.tool_name = "dungeon-list-sprites";
150 call.args.emplace("room", ExtractRoomId(normalized));
151 response.text_response =
152 "Let me inspect the dungeon room sprites for you.";
153 response.reasoning =
154 "Calling the sprite inspection tool provides precise coordinates for the agent.";
155 response.tool_calls.push_back(call);
156 return response;
157 }
158
159 if (absl::StrContains(normalized, "describe") &&
160 absl::StrContains(normalized, "room")) {
161 ToolCall call;
162 call.tool_name = "dungeon-describe-room";
163 call.args.emplace("room", ExtractRoomId(normalized));
164 response.text_response =
165 "I'll summarize the room's metadata and hazards.";
166 response.reasoning =
167 "Room description tool surfaces lighting, effects, and object counts before planning edits.";
168 response.tool_calls.push_back(call);
169 return response;
170 }
171
172 response.text_response =
173 "I'm just a mock service. Please load a provider like ollama or gemini.";
174 return response;
175}
176
177absl::StatusOr<AgentResponse> MockAIService::GenerateResponse(
178 const std::vector<agent::ChatMessage>& history) {
179 if (history.empty()) {
180 return absl::InvalidArgumentError("History cannot be empty.");
181 }
182
183 // If the last message in history is a tool output, synthesize a summary.
184 for (auto it = history.rbegin(); it != history.rend(); ++it) {
185 if (it->sender == agent::ChatMessage::Sender::kAgent &&
186 (absl::StrContains(it->message, "=== ") ||
187 absl::StrContains(it->message, "\"id\"") ||
188 absl::StrContains(it->message, "\n{"))) {
189 AgentResponse response;
190 response.text_response =
191 "Here's what I found:\n" + it->message +
192 "\nLet me know if you'd like to make a change.";
193 response.reasoning =
194 "Summarized the latest tool output for the user.";
195 return response;
196 }
197 }
198
199 auto user_it = std::find_if(history.rbegin(), history.rend(),
200 [](const agent::ChatMessage& message) {
201 return message.sender ==
202 agent::ChatMessage::Sender::kUser;
203 });
204 if (user_it == history.rend()) {
205 return absl::InvalidArgumentError(
206 "History does not contain a user message.");
207 }
208
209 return GenerateResponse(user_it->message);
210}
211
212} // namespace cli
213} // namespace yaze
214
absl::StatusOr< AgentResponse > GenerateResponse(const std::string &prompt) override
Definition ai_service.cc:89
std::string ExtractKeyword(const std::string &normalized_prompt)
Definition ai_service.cc:55
std::string ExtractRoomId(const std::string &normalized_prompt)
Definition ai_service.cc:18
Main namespace for the application.
Definition controller.cc:20
std::vector< std::string > commands
Definition common.h:26
std::string reasoning
Definition common.h:29
std::vector< ToolCall > tool_calls
Definition common.h:23
std::string text_response
Definition common.h:20
std::map< std::string, std::string > args
Definition common.h:14
std::string tool_name
Definition common.h:13