* refactor(vector): add injectable executor for the embedding daemon bridge await_with_timeout spawned coroutines unconditionally on the daemon global IO context, coupling the vector-layer embedding generator to daemon globals. EmbeddingConfig gains an optional boost::asio::any_io_executor executorOverride; the bridge uses it when present and keeps the global executor as the default — identical behavior for all existing callers (embedding_service, model providers, CLI all construct without an override). Lead 1 plan step (b) of the embedding_generator vector->daemon decomposition (Brick audit follow-up). Validation: full ninja build green; meson test yams:service_manager, vector_submodule, yams:embedding_lifecycle, yams:embedding_recovery — all green. Signed-off-by: trvon <me@trevon.dev> * refactor(vector): split DaemonBackend into own TU Keep daemon client/IPC headers out of the vector-layer generator TU; EmbeddingGenerator builds the backend via createDaemonBackend factory on the existing IEmbeddingBackend seam. Behavior-preserving. Brick audit lead 1: 5 file-level cycles hubbed on embedding_generator.cpp. Signed-off-by: trvon <me@trevon.dev> * revert(vector): keep bridge executor out of public EmbeddingConfig Pre-push gate caught it: the config layer includes the vector header transitively (ConfigResolver.h -> document_chunker.h -> embedding_generator.h) and has no boost asio include path, so <boost/asio/any_io_executor.hpp> in the public header breaks the config build. Executor selection stays in embedding_daemon_backend.cpp (daemon-facing TU by design); per-instance injection needs the config->vector include direction fixed first — recorded as follow-up. Signed-off-by: trvon <me@trevon.dev> * refactor(vector): trust resolved backend policy Remove EmbeddingGenerator's ambient ConfigResolver read. Callers now own backend-policy resolution; EmbeddingService marks its already-resolved runtime policy unconditionally. Keep TuneAdvisor only for the process-wide concurrency gate, where instance-local policy would not coordinate admission. Tests pin typed backend authority against conflicting environment state. Signed-off-by: trvon <me@trevon.dev> * chore(vector): remove stale config include The generator no longer reads config helpers after backend policy moved to callers, so drop the misleading DaemonBackend comment and unused include. Signed-off-by: trvon <me@trevon.dev> --------- Signed-off-by: trvon <me@trevon.dev>
162 lines
5.4 KiB
C++
162 lines
5.4 KiB
C++
#include <catch2/catch_test_macros.hpp>
|
|
|
|
#include <yams/compat/unistd.h>
|
|
#include <yams/vector/embedding_generator.h>
|
|
#include <yams/vector/simeon_embedding_backend.h>
|
|
|
|
#include <array>
|
|
#include <cmath>
|
|
#include <cstdlib>
|
|
#include <string>
|
|
#include <utility>
|
|
#include <vector>
|
|
#include <catch2/matchers/catch_matchers_string.hpp>
|
|
|
|
#include "../../common/test_helpers_catch2.h"
|
|
|
|
using namespace yams::vector;
|
|
using Catch::Matchers::StartsWith;
|
|
|
|
namespace {
|
|
|
|
EmbeddingConfig make_cfg(size_t dim = 256) {
|
|
EmbeddingConfig cfg;
|
|
cfg.backend = EmbeddingConfig::Backend::Simeon;
|
|
cfg.backend_is_resolved = true;
|
|
cfg.embedding_dim = dim;
|
|
cfg.normalize_embeddings = true;
|
|
cfg.max_sequence_length = 512;
|
|
return cfg;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
TEST_CASE("SimeonBackend initializes and reports its dimension", "[vector][simeon]") {
|
|
auto backend = makeSimeonBackend(make_cfg(384));
|
|
REQUIRE(backend != nullptr);
|
|
REQUIRE(backend->isAvailable());
|
|
REQUIRE(backend->initialize());
|
|
REQUIRE(backend->isInitialized());
|
|
REQUIRE(backend->getEmbeddingDimension() == 384u);
|
|
REQUIRE(backend->getBackendName() == "Simeon");
|
|
}
|
|
|
|
TEST_CASE("SimeonBackend fixed hash profile exposes the frozen retrieval space",
|
|
"[vector][simeon][identity]") {
|
|
auto cfg = make_cfg(1024);
|
|
cfg.simeon_encoder_profile = EmbeddingConfig::SimeonEncoderProfile::FixedHash384;
|
|
|
|
auto backend = makeSimeonBackend(cfg);
|
|
REQUIRE(backend != nullptr);
|
|
REQUIRE(backend->initialize());
|
|
CHECK(backend->getEmbeddingDimension() == 384u);
|
|
CHECK(backend->getEmbeddingSpaceIdentity() == "simeon-v1-384");
|
|
CHECK(simeonEmbeddingSpaceIdentity(cfg) == "simeon-v1-384");
|
|
}
|
|
|
|
TEST_CASE("SimeonBackend produces deterministic, unit-norm embeddings", "[vector][simeon]") {
|
|
auto backend = makeSimeonBackend(make_cfg(256));
|
|
REQUIRE(backend->initialize());
|
|
|
|
const std::string text = "The quick brown fox jumps over the lazy dog.";
|
|
auto r1 = backend->generateEmbedding(text);
|
|
auto r2 = backend->generateEmbedding(text);
|
|
REQUIRE(r1);
|
|
REQUIRE(r2);
|
|
REQUIRE(r1.value().size() == 256u);
|
|
REQUIRE(r1.value() == r2.value());
|
|
|
|
double acc = 0.0;
|
|
for (float f : r1.value())
|
|
acc += static_cast<double>(f) * static_cast<double>(f);
|
|
REQUIRE(std::fabs(std::sqrt(acc) - 1.0) < 1e-4);
|
|
}
|
|
|
|
TEST_CASE("SimeonBackend batch matches per-item encoding", "[vector][simeon]") {
|
|
auto backend = makeSimeonBackend(make_cfg(128));
|
|
REQUIRE(backend->initialize());
|
|
|
|
const std::vector<std::string> texts = {"alpha bravo charlie", "delta echo foxtrot",
|
|
"golf hotel india"};
|
|
auto per = std::vector<std::vector<float>>{};
|
|
for (const auto& t : texts) {
|
|
auto r = backend->generateEmbedding(t);
|
|
REQUIRE(r);
|
|
per.push_back(std::move(r).value());
|
|
}
|
|
auto batch = backend->generateEmbeddings(std::span<const std::string>(texts));
|
|
REQUIRE(batch);
|
|
REQUIRE(batch.value().size() == per.size());
|
|
for (size_t i = 0; i < per.size(); ++i) {
|
|
REQUIRE(batch.value()[i] == per[i]);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("EmbeddingGenerator trusts typed backend over ambient environment",
|
|
"[vector][simeon][config]") {
|
|
yams::test::ScopedEnvVar backend{"YAMS_EMBED_BACKEND", std::string{"daemon"}};
|
|
|
|
EmbeddingConfig cfg;
|
|
cfg.backend = EmbeddingConfig::Backend::Simeon;
|
|
cfg.embedding_dim = 384;
|
|
cfg.normalize_embeddings = true;
|
|
EmbeddingGenerator gen(cfg);
|
|
REQUIRE(gen.initialize());
|
|
REQUIRE(gen.getBackendName() == "Simeon");
|
|
REQUIRE(gen.getEmbeddingDimension() == 384u);
|
|
|
|
auto v = gen.generateEmbedding("hello world");
|
|
REQUIRE(v.size() == 384u);
|
|
}
|
|
|
|
TEST_CASE("EmbeddingGenerator selects Simeon when configured directly", "[vector][simeon]") {
|
|
EmbeddingConfig cfg;
|
|
cfg.backend = EmbeddingConfig::Backend::Simeon;
|
|
cfg.backend_is_resolved = true;
|
|
cfg.embedding_dim = 128;
|
|
cfg.normalize_embeddings = true;
|
|
|
|
EmbeddingGenerator gen(cfg);
|
|
REQUIRE(gen.initialize());
|
|
REQUIRE(gen.getBackendName() == "Simeon");
|
|
REQUIRE(gen.getEmbeddingDimension() == 128u);
|
|
}
|
|
|
|
TEST_CASE("EmbeddingGenerator moved-from observers report neutral state",
|
|
"[vector][embedding-generator][move][catch2]") {
|
|
EmbeddingGenerator source(make_cfg(128));
|
|
EmbeddingGenerator destination(std::move(source));
|
|
|
|
CHECK_FALSE(source.isInitialized());
|
|
CHECK((source.estimateTokenCount("moved-from") == 0));
|
|
CHECK((source.getLastError().empty()));
|
|
CHECK_FALSE(source.hasError());
|
|
CHECK((destination.getConfig().embedding_dim == 128));
|
|
}
|
|
|
|
TEST_CASE("EmbeddingGenerator maps OnnxRuntime config to Daemon path", "[vector][daemon]") {
|
|
EmbeddingConfig cfg;
|
|
cfg.backend = EmbeddingConfig::Backend::OnnxRuntime;
|
|
cfg.backend_is_resolved = true;
|
|
|
|
EmbeddingGenerator gen(cfg);
|
|
REQUIRE_THAT(gen.getBackendName(), StartsWith("Daemon"));
|
|
}
|
|
|
|
TEST_CASE("EmbeddingGenerator maps legacy Hybrid config to Daemon path", "[vector][daemon]") {
|
|
EmbeddingConfig cfg;
|
|
cfg.backend = EmbeddingConfig::Backend::Hybrid;
|
|
cfg.backend_is_resolved = true;
|
|
|
|
EmbeddingGenerator gen(cfg);
|
|
REQUIRE_THAT(gen.getBackendName(), StartsWith("Daemon"));
|
|
}
|
|
|
|
TEST_CASE("EmbeddingGenerator selects Daemon when configured directly", "[vector][daemon]") {
|
|
EmbeddingConfig cfg;
|
|
cfg.backend = EmbeddingConfig::Backend::Daemon;
|
|
cfg.backend_is_resolved = true;
|
|
|
|
EmbeddingGenerator gen(cfg);
|
|
REQUIRE_THAT(gen.getBackendName(), StartsWith("Daemon"));
|
|
}
|