yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
ai_service.cc
Go to the documentation of this file.
2
3#include <algorithm>
4#include <cctype>
5#include <sstream>
6
7#include "absl/strings/ascii.h"
8#include "absl/strings/match.h"
9#include "absl/strings/numbers.h"
10#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", "please",
58 "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(),
73 [](unsigned char c) {
74 return !std::isalnum(c) && c != '_' &&
75 c != '-';
76 }),
77 token.end());
78 if (token.empty()) {
79 continue;
80 }
81 if (!is_stopword(token)) {
82 return token;
83 }
84 }
85
86 return "all";
87}
88
89} // namespace
90
91absl::StatusOr<AgentResponse> MockAIService::GenerateResponse(
92 const std::string& prompt) {
93 AgentResponse response;
94 response.provider = "mock";
95 response.model = "mock";
96 response.parameters["mode"] = "scripted";
97 response.parameters["temperature"] = "0.0";
98 const std::string normalized = absl::AsciiStrToLower(prompt);
99
100 if (normalized.empty()) {
101 response.text_response =
102 "Let's start with a prompt about the overworld or dungeons.";
103 return response;
104 }
105
106 if (absl::StrContains(normalized, "place") &&
107 absl::StrContains(normalized, "tree")) {
108 response.text_response =
109 "Sure, I can do that. Here's the command to place a tree.";
110 response.commands.push_back(
111 "overworld set-tile --map 0 --x 10 --y 20 --tile 0x02E");
112 response.reasoning =
113 "The user asked to place a tree tile16, so I generated the matching "
114 "set-tile command.";
115 return response;
116 }
117
118 if (absl::StrContains(normalized, "list") &&
119 absl::StrContains(normalized, "resource")) {
120 std::string resource_type = "dungeon";
121 if (absl::StrContains(normalized, "overworld")) {
122 resource_type = "overworld";
123 } else if (absl::StrContains(normalized, "sprite")) {
124 resource_type = "sprite";
125 } else if (absl::StrContains(normalized, "palette")) {
126 resource_type = "palette";
127 }
128
129 ToolCall call;
130 call.tool_name = "resource-list";
131 call.args.emplace("type", resource_type);
132 response.text_response =
133 absl::StrFormat("Fetching %s labels from the ROM...", resource_type);
134 response.reasoning =
135 "Using the resource-list tool keeps the LLM in sync with project "
136 "labels.";
137 response.tool_calls.push_back(call);
138 return response;
139 }
140
141 if (absl::StrContains(normalized, "search") &&
142 (absl::StrContains(normalized, "resource") ||
143 absl::StrContains(normalized, "label"))) {
144 ToolCall call;
145 call.tool_name = "resource-search";
146 call.args.emplace("query", ExtractKeyword(normalized));
147 response.text_response =
148 "Let me look through the labelled resources for matches.";
149 response.reasoning =
150 "Resource search provides fuzzy matching against the ROM label "
151 "catalogue.";
152 response.tool_calls.push_back(call);
153 return response;
154 }
155
156 if (absl::StrContains(normalized, "sprite") &&
157 absl::StrContains(normalized, "room")) {
158 ToolCall call;
159 call.tool_name = "dungeon-list-sprites";
160 call.args.emplace("room", ExtractRoomId(normalized));
161 response.text_response = "Let me inspect the dungeon room sprites for you.";
162 response.reasoning =
163 "Calling the sprite inspection tool provides precise coordinates for "
164 "the agent.";
165 response.tool_calls.push_back(call);
166 return response;
167 }
168
169 if (absl::StrContains(normalized, "describe") &&
170 absl::StrContains(normalized, "room")) {
171 ToolCall call;
172 call.tool_name = "dungeon-describe-room";
173 call.args.emplace("room", ExtractRoomId(normalized));
174 response.text_response = "I'll summarize the room's metadata and hazards.";
175 response.reasoning =
176 "Room description tool surfaces lighting, effects, and object counts "
177 "before planning edits.";
178 response.tool_calls.push_back(call);
179 return response;
180 }
181
182 response.text_response =
183 "I'm just a mock service. Please load a provider like ollama or gemini.";
184 return response;
185}
186
187absl::StatusOr<AgentResponse> MockAIService::GenerateResponse(
188 const std::vector<agent::ChatMessage>& history) {
189 if (history.empty()) {
190 return absl::InvalidArgumentError("History cannot be empty.");
191 }
192
193 // If the last message in history is a tool output, synthesize a summary.
194 for (auto it = history.rbegin(); it != history.rend(); ++it) {
195 if (it->sender == agent::ChatMessage::Sender::kAgent &&
196 (absl::StrContains(it->message, "=== ") ||
197 absl::StrContains(it->message, "\"id\"") ||
198 absl::StrContains(it->message, "\n{"))) {
199 AgentResponse response;
200 response.provider = "mock";
201 response.model = "mock";
202 response.parameters["mode"] = "scripted";
203 response.parameters["temperature"] = "0.0";
204 response.text_response = "Here's what I found:\n" + it->message +
205 "\nLet me know if you'd like to make a change.";
206 response.reasoning = "Summarized the latest tool output for the user.";
207 return response;
208 }
209 }
210
211 auto user_it = std::find_if(
212 history.rbegin(), history.rend(), [](const agent::ChatMessage& message) {
213 return message.sender == agent::ChatMessage::Sender::kUser;
214 });
215 if (user_it == history.rend()) {
216 return absl::InvalidArgumentError(
217 "History does not contain a user message.");
218 }
219
220 return GenerateResponse(user_it->message);
221}
222
223} // namespace cli
224} // namespace yaze
absl::StatusOr< AgentResponse > GenerateResponse(const std::string &prompt) override
Definition ai_service.cc:91
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
std::vector< std::string > commands
Definition common.h:26
std::string model
Definition common.h:33
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::string provider
Definition common.h:32
std::map< std::string, std::string > parameters
Definition common.h:37
std::map< std::string, std::string > args
Definition common.h:14
std::string tool_name
Definition common.h:13