mirror of
https://github.com/aaronpo97/the-biergarten-app.git
synced 2026-07-16 17:47:22 +00:00
refactor(pipeline): restructure config, add PromptDirectory, consolidate SQLite layer (#217)
* Refactor ApplicationOptions to separate config concerns * add prompt dir app option * readability updates: remove magic numbers, update comments * codebase formatting * Update docs * Extract argument parsing, timer out of
This commit is contained in:
@@ -33,6 +33,9 @@ static std::string FormatLocalLanguageCodes(
|
||||
return formatted;
|
||||
}
|
||||
|
||||
// GBNF grammar for structured brewery JSON output.
|
||||
// @TODO move to a separate gbnf file if it grows in complexity or is shared
|
||||
// across modules.
|
||||
static constexpr std::string_view kBreweryJsonGrammar = R"json_brewery(
|
||||
root ::= thought-block "{" ws "\"name_en\"" ws ":" ws string ws "," ws "\"description_en\"" ws ":" ws string ws "," ws "\"name_local\"" ws ":" ws string ws "," ws "\"description_local\"" ws ":" ws string ws "}" ws
|
||||
thought-block ::= [^{]*
|
||||
@@ -59,11 +62,12 @@ BreweryResult LlamaGenerator::GenerateBrewery(
|
||||
location.country.empty() ? std::string{}
|
||||
: std::format(", {}", location.country);
|
||||
/**
|
||||
* Load brewery system prompt from file
|
||||
* Falls back to minimal inline prompt if file not found
|
||||
* Load brewery system prompt via the injected prompt directory.
|
||||
* The key "BREWERY_GENERATION" resolves to BREWERY_GENERATION.md inside
|
||||
* the configured --prompt-dir. Throws on missing or empty file.
|
||||
*/
|
||||
const std::string system_prompt =
|
||||
LoadBrewerySystemPrompt("prompts/system.md");
|
||||
prompt_directory_->Load("BREWERY_GENERATION");
|
||||
|
||||
std::string user_prompt = std::format(
|
||||
"## CITY:\n{}\n\n## COUNTRY:\n{}\n\n## LOCAL LANGUAGE CODES:\n{}\n\n## "
|
||||
|
||||
@@ -12,6 +12,13 @@
|
||||
#include "data_generation/llama_generator.h"
|
||||
#include "data_generation/llama_generator_helpers.h"
|
||||
|
||||
// TODO: Implement locale-aware user profile generation.
|
||||
// Current implementation returns a hardcoded test value and ignores the
|
||||
// locale parameter. Future implementation should:
|
||||
// 1. Load a USER_GENERATION.md prompt template with locale context
|
||||
// 2. Perform LLM inference with locale-specific username/bio generation
|
||||
// 3. Parse and validate JSON output with retry handling (similar to brewery)
|
||||
// 4. Return locale-aware username and biography
|
||||
UserResult LlamaGenerator::GenerateUser(const std::string& locale) {
|
||||
return {.username = "test_user",
|
||||
.bio = "This is a test user profile from " + locale + "."};
|
||||
|
||||
@@ -58,6 +58,11 @@ static std::string CondenseWhitespace(std::string_view text) {
|
||||
return out;
|
||||
}
|
||||
|
||||
// Guard against truncating in the first half of the string.
|
||||
// This preserves the critical opening content and avoids cutting critical
|
||||
// context words early in the region description.
|
||||
static constexpr size_t kTruncationGuardDivisor = 2;
|
||||
|
||||
/**
|
||||
* Truncate region context to fit within max length while preserving word
|
||||
* boundaries
|
||||
@@ -71,7 +76,8 @@ std::string PrepareRegionContext(std::string_view region_context,
|
||||
|
||||
normalized.resize(max_chars);
|
||||
const size_t last_space = normalized.find_last_of(' ');
|
||||
if (last_space != std::string::npos && last_space > max_chars / 2) {
|
||||
if (last_space != std::string::npos &&
|
||||
last_space > max_chars / kTruncationGuardDivisor) {
|
||||
normalized.resize(last_space);
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,9 @@
|
||||
#include "llama.h"
|
||||
|
||||
static constexpr size_t kPromptTokenSlack = 8;
|
||||
// Minimum tokens to keep when using top-p sampling. Ensures at least one
|
||||
// candidate token remains available even with very restrictive top-p values.
|
||||
static constexpr size_t kTopPMinKeep = 1;
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -62,7 +65,7 @@ SamplerHandle MakeSamplerChain(const llama_vocab* vocab,
|
||||
"LlamaGenerator: failed to initialize temperature sampler");
|
||||
add_sampler(llama_sampler_init_top_k(static_cast<int32_t>(config.top_k)),
|
||||
"LlamaGenerator: failed to initialize top-k sampler");
|
||||
add_sampler(llama_sampler_init_top_p(config.top_p, 1),
|
||||
add_sampler(llama_sampler_init_top_p(config.top_p, kTopPMinKeep),
|
||||
"LlamaGenerator: failed to initialize top-p sampler");
|
||||
add_sampler(llama_sampler_init_dist(config.seed),
|
||||
"LlamaGenerator: failed to initialize distribution sampler");
|
||||
|
||||
@@ -32,9 +32,11 @@ void LlamaGenerator::ContextDeleter::operator()(
|
||||
|
||||
LlamaGenerator::LlamaGenerator(
|
||||
const ApplicationOptions& options, const std::string& model_path,
|
||||
std::unique_ptr<IPromptFormatter> prompt_formatter)
|
||||
std::unique_ptr<IPromptFormatter> prompt_formatter,
|
||||
std::unique_ptr<IPromptDirectory> prompt_directory)
|
||||
: rng_(std::random_device{}()),
|
||||
prompt_formatter_(std::move(prompt_formatter)) {
|
||||
prompt_formatter_(std::move(prompt_formatter)),
|
||||
prompt_directory_(std::move(prompt_directory)) {
|
||||
if (model_path.empty()) {
|
||||
throw std::runtime_error("LlamaGenerator: model path must not be empty");
|
||||
}
|
||||
@@ -44,41 +46,49 @@ LlamaGenerator::LlamaGenerator(
|
||||
"LlamaGenerator: prompt formatter dependency must not be null");
|
||||
}
|
||||
|
||||
if (options.temperature < 0.0F) {
|
||||
if (!prompt_directory_) {
|
||||
throw std::runtime_error(
|
||||
"LlamaGenerator: prompt directory dependency must not be null");
|
||||
}
|
||||
|
||||
const auto sampling = options.generator.sampling.value_or(SamplingOptions{});
|
||||
|
||||
if (sampling.temperature < 0.0F) {
|
||||
throw std::runtime_error(
|
||||
"LlamaGenerator: sampling temperature must be >= 0");
|
||||
}
|
||||
|
||||
if (options.top_p <= 0.0F || options.top_p > 1.0F) {
|
||||
if (sampling.top_p <= 0.0F || sampling.top_p > 1.0F) {
|
||||
throw std::runtime_error(
|
||||
"LlamaGenerator: sampling top-p must be in (0, 1]");
|
||||
}
|
||||
|
||||
if (options.top_k == 0U) {
|
||||
if (sampling.top_k == 0U) {
|
||||
throw std::runtime_error("LlamaGenerator: sampling top-k must be > 0");
|
||||
}
|
||||
|
||||
if (options.seed < -1) {
|
||||
if (sampling.seed < -1) {
|
||||
throw std::runtime_error(
|
||||
"LlamaGenerator: seed must be >= 0, or -1 for random");
|
||||
}
|
||||
|
||||
if (options.n_ctx == 0 || options.n_ctx > kMaxContextSize) {
|
||||
if (sampling.n_ctx == 0 || sampling.n_ctx > kMaxContextSize) {
|
||||
throw std::runtime_error(
|
||||
"LlamaGenerator: context size must be in range [1, 32768]");
|
||||
}
|
||||
|
||||
sampling_temperature_ = options.temperature;
|
||||
sampling_top_p_ = options.top_p;
|
||||
sampling_top_k_ = options.top_k;
|
||||
sampling_temperature_ = sampling.temperature;
|
||||
sampling_top_p_ = sampling.top_p;
|
||||
sampling_top_k_ = sampling.top_k;
|
||||
|
||||
if (options.seed == -1) {
|
||||
if (sampling.seed == -1) {
|
||||
std::random_device random_device;
|
||||
rng_.seed(random_device());
|
||||
} else {
|
||||
rng_.seed(static_cast<uint32_t>(options.seed));
|
||||
rng_.seed(static_cast<uint32_t>(sampling.seed));
|
||||
}
|
||||
n_ctx_ = options.n_ctx;
|
||||
|
||||
n_ctx_ = sampling.n_ctx;
|
||||
|
||||
this->Load(model_path);
|
||||
}
|
||||
|
||||
@@ -14,6 +14,10 @@
|
||||
#include "data_generation/llama_generator.h"
|
||||
#include "llama.h"
|
||||
|
||||
// Maximum batch size for decode operations. Capping the batch prevents
|
||||
// excessive memory allocation while maintaining inference performance.
|
||||
static constexpr uint32_t kMaxBatchSize = 5000U;
|
||||
|
||||
void LlamaGenerator::Load(const std::string& model_path) {
|
||||
context_.reset();
|
||||
model_.reset();
|
||||
@@ -28,7 +32,7 @@ void LlamaGenerator::Load(const std::string& model_path) {
|
||||
|
||||
llama_context_params context_params = llama_context_default_params();
|
||||
context_params.n_ctx = n_ctx_;
|
||||
context_params.n_batch = std::min(n_ctx_, static_cast<uint32_t>(5000));
|
||||
context_params.n_batch = std::min(n_ctx_, kMaxBatchSize);
|
||||
|
||||
LlamaGenerator::ContextHandle loaded_context(
|
||||
llama_init_from_model(loaded_model.get(), context_params));
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
/**
|
||||
* @file data_generation/llama/load_brewery_prompt.cc
|
||||
* @brief Resolves brewery system prompt content from cache or a configured
|
||||
* filesystem path and provides a robust inline fallback prompt when absent.
|
||||
*/
|
||||
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <stdexcept>
|
||||
|
||||
#include "data_generation/llama_generator.h"
|
||||
|
||||
/**
|
||||
* @brief Loads brewery system prompt from disk or cache.
|
||||
*
|
||||
* @param prompt_file_path Preferred prompt file location.
|
||||
* @return Prompt text loaded from disk.
|
||||
*/
|
||||
std::string LlamaGenerator::LoadBrewerySystemPrompt(
|
||||
const std::filesystem::path& prompt_file_path) {
|
||||
// Return cached version if already loaded
|
||||
if (!brewery_system_prompt_.empty()) {
|
||||
return brewery_system_prompt_;
|
||||
}
|
||||
|
||||
std::ifstream prompt_file(prompt_file_path);
|
||||
if (!prompt_file.is_open()) {
|
||||
spdlog::error(
|
||||
"LlamaGenerator: Failed to open brewery system prompt file '{}'",
|
||||
prompt_file_path.string());
|
||||
throw std::runtime_error(
|
||||
"LlamaGenerator: missing brewery system prompt file: " +
|
||||
prompt_file_path.string());
|
||||
}
|
||||
|
||||
const std::string prompt((std::istreambuf_iterator(prompt_file)),
|
||||
std::istreambuf_iterator<char>());
|
||||
prompt_file.close();
|
||||
|
||||
if (prompt.empty()) {
|
||||
spdlog::error("LlamaGenerator: Brewery system prompt file '{}' is empty",
|
||||
prompt_file_path.string());
|
||||
throw std::runtime_error(
|
||||
"LlamaGenerator: empty brewery system prompt file: " +
|
||||
prompt_file_path.string());
|
||||
}
|
||||
|
||||
spdlog::info(
|
||||
"LlamaGenerator: Loaded brewery system prompt from '{}' ({} chars)",
|
||||
prompt_file_path.string(), prompt.length());
|
||||
brewery_system_prompt_ = prompt;
|
||||
return brewery_system_prompt_;
|
||||
}
|
||||
Reference in New Issue
Block a user