Update generate pipeline to add OpenAI api as an additional backend (for comparison testing), add brewery coordinates (#244)

* add brewery coordinates

* Add openai mode

* Add coordinates to schema

* Fix C++ review findings in pipeline: k-prefixed enum values, include ordering, populated validation error messages, conventional const notation, and endl replaced with newline
This commit is contained in:
2026-08-23 15:49:28 -04:00
committed by Aaron Po
parent 31da1d557c
commit e738fe484a
35 changed files with 1327 additions and 343 deletions

View File

@@ -1,4 +1,5 @@
#include <chrono>
#include <cstdlib>
#include <format>
#include <iostream>
#include <optional>
@@ -37,13 +38,20 @@ std::optional<ApplicationOptions> ParseArguments(
"Number of layers to offload to GPU");
};
// --mocked and --model are mutually exclusive; validation is enforced below
// rather than at registration to produce a clear diagnostic message.
// --mocked, --model, and --openai are mutually exclusive; validation is
// enforced below rather than at registration to produce a clear
// diagnostic message.
auto add_generator_options = [&]() -> void {
opt("mocked", prog_opts::bool_switch(),
"Use mocked generator for brewery/user data");
opt("model,m", prog_opts::value<std::string>()->default_value(""),
"Path to LLM model (gguf)");
opt("openai", prog_opts::bool_switch(),
"Use the OpenAI API (Chat Completions) for brewery/user data. "
"Requires the OPENAI_API_KEY environment variable.");
opt("openai-model",
prog_opts::value<std::string>()->default_value("gpt-4o-mini"),
"OpenAI model ID used when --openai is set");
};
auto add_pipeline_options = [&]() -> void {
@@ -109,14 +117,19 @@ std::optional<ApplicationOptions> ParseArguments(
const bool use_mocked = var_map["mocked"].as<bool>();
const std::string model_path = var_map["model"].as<std::string>();
const bool use_openai = var_map["openai"].as<bool>();
const int n_gpu_layers = var_map["n-gpu-layers"].as<int>();
// Enforce mutual exclusivity before any further configuration is
// applied.
if (use_mocked && !model_path.empty()) {
const int selected_generator_count =
(use_mocked ? 1 : 0) + (!model_path.empty() ? 1 : 0) +
(use_openai ? 1 : 0);
if (selected_generator_count > 1) {
const std::string msg =
"Invalid arguments: --mocked and --model are mutually "
"exclusive";
"Invalid arguments: --mocked, --model, and --openai are "
"mutually exclusive";
if (logger) {
logger->Log(LogDTO{.level = LogLevel::Error,
.phase = PipelinePhase::Startup,
@@ -127,10 +140,10 @@ std::optional<ApplicationOptions> ParseArguments(
return std::nullopt;
}
if (!use_mocked && model_path.empty()) {
if (selected_generator_count == 0) {
const std::string msg =
"Invalid arguments: either --mocked or --model must be "
"specified";
"Invalid arguments: exactly one of --mocked, --model, or "
"--openai must be specified";
if (logger) {
logger->Log(LogDTO{.level = LogLevel::Error,
.phase = PipelinePhase::Startup,
@@ -141,8 +154,9 @@ std::optional<ApplicationOptions> ParseArguments(
return std::nullopt;
}
// Prompt directory is only meaningful for live inference — the mock
// generator has no use for it and should not require it to be present.
// Prompt directory is only meaningful for live inference (Llama or
// OpenAI) — the mock generator has no use for it and should not
// require it to be present.
if (!use_mocked && options.pipeline.prompt_dir.empty()) {
const std::string msg =
"Invalid arguments: --prompt-dir is required when not using "
@@ -157,8 +171,29 @@ std::optional<ApplicationOptions> ParseArguments(
return std::nullopt;
}
options.generator.use_mocked = use_mocked;
if (use_openai) {
const char* api_key_env = std::getenv("OPENAI_API_KEY");
if (api_key_env == nullptr || api_key_env[0] == '\0') {
const std::string msg =
"Invalid arguments: the OPENAI_API_KEY environment "
"variable must be set when using --openai";
if (logger) {
logger->Log({.level = LogLevel::Error,
.phase = PipelinePhase::Startup,
.message = msg});
} else {
std::cerr << msg << '\n';
}
return std::nullopt;
}
options.generator.openai_api_key = api_key_env;
}
options.generator.mode = use_mocked ? GeneratorMode::kMock
: use_openai ? GeneratorMode::kOpenAI
: GeneratorMode::kLlama;
options.generator.model_path = model_path;
options.generator.openai_model = var_map["openai-model"].as<std::string>();
// Only populate sampling config when the user explicitly overrides at
// least one value. Leaving it as std::nullopt lets LlamaGenerator fall
@@ -172,10 +207,11 @@ std::optional<ApplicationOptions> ParseArguments(
if (user_provided_sampling) {
// Warn but do not fail — the run is still valid, the flags are just
// silently irrelevant when no model is loaded.
if (use_mocked) {
// silently irrelevant when no local model is loaded.
if (options.generator.mode != GeneratorMode::kLlama) {
const std::string msg =
"Sampling parameters are ignored when using --mocked";
"Sampling parameters are ignored unless using --model "
"(Llama)";
if (logger) {
logger->Log(LogDTO{.level = LogLevel::Warn,
.phase = PipelinePhase::Startup,