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