fix: stabilize Gemma 4 brewery generation

remove misleading turn-token output guidance from the brewery prompt
extract the last balanced JSON object before validation
keep README model setup and run instructions aligned
preserve Gemma 4 sampling defaults and local model usage
This commit is contained in:
Aaron Po
2026-04-10 22:25:26 -04:00
parent 902bda6eb9
commit 8c572a2d07
5 changed files with 74 additions and 44 deletions

View File

@@ -26,8 +26,9 @@ auto ExtractFinalJsonPayload(std::string raw_response) -> std::string {
return text.substr(first, last - first + 1);
};
static const std::array<std::string_view, 4> separator_tokens = {
"<|turn|>", "<turn|>", "<channel|>", "<|channel|>"};
static const std::array<std::string_view, 6> separator_tokens = {
"<|think|>", "<think|>", "<|turn|>",
"<turn|>", "<channel|>", "<|channel|>"};
std::size_t separator_pos = std::string::npos;
std::size_t separator_length = 0;
@@ -46,18 +47,13 @@ auto ExtractFinalJsonPayload(std::string raw_response) -> std::string {
}
const std::string_view trimmed = trim(raw_response);
const std::size_t first_brace = trimmed.find('{');
if (first_brace == std::string_view::npos) {
return std::string(trimmed);
std::string json_candidate =
ExtractLastJsonObjectPublic(std::string(trimmed));
if (!json_candidate.empty()) {
return ExtractLastJsonObjectPublic(std::string(trimmed));
}
const std::size_t last_brace = trimmed.find_last_of('}');
if (last_brace == std::string_view::npos || last_brace < first_brace) {
return std::string(trimmed.substr(first_brace));
}
return std::string(
trimmed.substr(first_brace, last_brace - first_brace + 1));
return std::string(trimmed);
}
} // namespace
@@ -147,9 +143,11 @@ auto LlamaGenerator::GenerateBrewery(const BreweryLocation& location,
// limits.
prompt =
"Your previous response was invalid. Error: " + validation_error +
"\nReturn ONLY valid JSON with this exact schema: "
"{\"name\": \"string\", \"description\": \"string\"}."
"\nDo not include markdown, comments, or extra keys.";
"\nReturn ONLY valid JSON with exactly these keys: "
"{\"name\": \"<brewery name>\", "
"\"description\": \"<single-paragraph description>\"}."
"\nDo not include markdown, comments, extra keys, or literal "
"placeholder values.";
prompt += "\n\n";
prompt += retry_location;
}

View File

@@ -263,12 +263,14 @@ static void AppendTokenPiece(const llama_vocab* vocab, llama_token token,
output.append(buffer.data(), static_cast<std::size_t>(bytes));
}
static bool ExtractFirstJsonObject(const std::string& text,
std::string& json_out) {
static bool ExtractLastJsonObject(const std::string& text,
std::string& json_out) {
std::size_t start = std::string::npos;
int depth = 0;
bool in_string = false;
bool escaped = false;
bool found = false;
std::string candidate;
for (std::size_t i = 0; i < text.size(); ++i) {
const char ch = text[i];
@@ -303,13 +305,27 @@ static bool ExtractFirstJsonObject(const std::string& text,
}
--depth;
if (depth == 0 && start != std::string::npos) {
json_out = text.substr(start, i - start + 1);
return true;
candidate = text.substr(start, i - start + 1);
found = true;
}
}
}
return false;
if (!found) {
return false;
}
json_out = std::move(candidate);
return true;
}
std::string ExtractLastJsonObjectPublic(const std::string& text) {
std::string extracted;
if (ExtractLastJsonObject(text, extracted)) {
return extracted;
}
return {};
}
static std::string ValidateBreweryJson(const std::string& raw,
@@ -371,7 +387,7 @@ static std::string ValidateBreweryJson(const std::string& raw,
std::string validation_error;
if (ec) {
std::string extracted;
if (!ExtractFirstJsonObject(raw, extracted)) {
if (!ExtractLastJsonObject(raw, extracted)) {
return "JSON parse error: " + ec.message();
}