Agentic AIC++verifiedVerified
Evaluator-Optimizer Agent Pattern in C++
An iterative refinement loop where an 'Evaluator' provides granular feedback on an 'Optimizer’s' output until quality thresholds are met.
How to Implement the Evaluator-Optimizer Agent Pattern in C++
1Step 1: Define the Feedback struct and callable types
struct Feedback {
bool isPass;
std::string critique;
double score;
};
using Optimizer = std::function<std::string(const std::string&)>;
using Refiner = std::function<std::string(const std::string&, const std::string&)>;
using Evaluator = std::function<Feedback(const std::string&)>;2Step 2: Implement the iterative refinement loop
constexpr int MAX_ITERATIONS = 5;
std::string refinementLoop(const std::string& task,
Optimizer generate,
Refiner refine,
Evaluator evaluate) {
std::string current = generate(task);
for (int i = 0; i < MAX_ITERATIONS; ++i) {
Feedback fb = evaluate(current);
if (fb.isPass) return current;
current = refine(current, fb.critique);
}
return current;
}3Step 3: Demonstrate the loop with stubs
int main() {
auto result = refinementLoop(
"Write a haiku",
[](const std::string& task) { return "first draft of " + task; },
[](const std::string& cur, const std::string& fb) {
return cur + " [refined: " + fb + "]";
},
[n = 0](const std::string&) mutable -> Feedback {
return {++n >= 3, "needs more detail", static_cast<double>(n) * 30.0};
}
);
std::cout << "Final: " << result << "\n";
}#include <iostream>
#include <string>
#include <vector>
#include <functional>
#include <optional>
#include <stdexcept>
#include <chrono>
#include <format>
#include <concepts>
// [step] Define concepts and structured types for type-safe refinement
template <typename T>
concept Scorable = requires(T t) {
{ t.score } -> std::convertible_to<double>;
{ t.isPass } -> std::convertible_to<bool>;
};
struct CategoryScore {
std::string name;
double value;
};
struct Feedback {
bool isPass;
std::string critique;
double score;
std::vector<CategoryScore> categories;
};
static_assert(Scorable<Feedback>);
struct RefinementConfig {
int maxIterations = 5;
double passThreshold = 80.0;
std::chrono::milliseconds timeout{30000};
};
struct RefinementResult {
std::string output;
int iterations;
double finalScore;
std::vector<Feedback> history;
};
// [step] Define the Optimizer and Evaluator interfaces
class IOptimizer {
public:
virtual ~IOptimizer() = default;
virtual std::string generate(const std::string& task) = 0;
virtual std::string refine(const std::string& current,
const std::string& critique) = 0;
};
class IEvaluator {
public:
virtual ~IEvaluator() = default;
virtual Feedback check(const std::string& output) = 0;
};
// [step] Implement the refinement engine with progress callback and timeout
using ProgressCallback = std::function<void(int iteration, const Feedback&)>;
class RefinementEngine {
RefinementConfig config_;
ProgressCallback onProgress_;
public:
explicit RefinementEngine(RefinementConfig config,
ProgressCallback cb = nullptr)
: config_(std::move(config)), onProgress_(std::move(cb)) {}
RefinementResult run(const std::string& task,
IOptimizer& optimizer,
IEvaluator& evaluator) {
auto start = std::chrono::steady_clock::now();
RefinementResult result;
result.output = optimizer.generate(task);
for (int i = 0; i < config_.maxIterations; ++i) {
auto elapsed = std::chrono::steady_clock::now() - start;
if (elapsed > config_.timeout) {
throw std::runtime_error(
std::format("Timeout after {} iterations", i));
}
Feedback fb = evaluator.check(result.output);
result.history.push_back(fb);
if (onProgress_) onProgress_(i + 1, fb);
if (fb.isPass || fb.score >= config_.passThreshold) {
result.iterations = i + 1;
result.finalScore = fb.score;
return result;
}
result.output = optimizer.refine(result.output, fb.critique);
}
Feedback finalFb = evaluator.check(result.output);
result.iterations = config_.maxIterations;
result.finalScore = finalFb.score;
result.history.push_back(finalFb);
return result;
}
};
// [step] Demo with concrete implementations
class StubOptimizer : public IOptimizer {
public:
std::string generate(const std::string& task) override {
return "Draft: " + task;
}
std::string refine(const std::string& current,
const std::string& critique) override {
return current + " [fixed: " + critique + "]";
}
};
class StubEvaluator : public IEvaluator {
int callCount_ = 0;
public:
Feedback check(const std::string&) override {
++callCount_;
double s = std::min(100.0, callCount_ * 35.0);
return {s >= 80.0, "Improve clarity", s, {{"clarity", s}}};
}
};
int main() {
StubOptimizer opt;
StubEvaluator eval;
RefinementEngine engine(
{.maxIterations = 5, .passThreshold = 80.0},
[](int i, const Feedback& fb) {
std::cout << std::format("Iteration {}: score={:.1f}\n", i, fb.score);
}
);
auto result = engine.run("Write a report", opt, eval);
std::cout << std::format("Done in {} iterations, score={:.1f}\n",
result.iterations, result.finalScore);
}Evaluator-Optimizer Agent Pattern Architecture
hourglass_empty
Rendering diagram...
lightbulb
Evaluator-Optimizer Agent Pattern in the Real World
“Think of a student writing an essay (Optimizer) and a teacher grading it with detailed feedback (Evaluator). The student revises based on the red-ink comments and resubmits. This cycle repeats until the essay meets the teacher’s standards—or the deadline (max iterations) is reached.”