yams/tests/benchmarks/search/retrieval_benchmark_support.h
Trevon 3f37e8f564
Sync experimental into main: Search retrieval improvements (#60)
* fix(graph): handle qualified C++ symbols

Signed-off-by: trvon <me@trevon.dev>

* refactor(vector)!: remove dead transition APIs

Split Simeon vector and retrieval linkage so consumers inherit only the runtime surface they use.

BREAKING CHANGE: remove unused async/model/config helpers and the undefined vector-backend factory from public headers.

Signed-off-by: trvon <me@trevon.dev>

* fix(vector): harden PQ snapshot lifecycle

Validate and atomically install persisted generations, revalidate after races, and fall back to exact search when a snapshot is stale. Add the exact-scan reference lane and distinguish observed work counters from unavailable backend metrics.

Signed-off-by: trvon <me@trevon.dev>

* feat(topology): expose route coordinates

Persist chart distortion and local-dimension evidence, retain pre-scalarization route axes, and emit detailed trace rows without treating unavailable vector work as zero.

Signed-off-by: trvon <me@trevon.dev>

* feat(xplan): add vector decision gates

Track binary and corpus identity, preserve missing-work semantics, and add repeated PQ routing, crossover, and rerank plans with quality and cost summaries.

Signed-off-by: trvon <me@trevon.dev>

* feat(topology): formalize coordinate routing

Separate chart geometry from route-risk admission and prove candidate-stage recall and strict-focus boundaries. Record the measured atlas limitations and literature basis for the next construction experiment.

Signed-off-by: trvon <me@trevon.dev>

* fix(search): serialize SymSpell readers

SQLite-backed lookups reuse mutable prepared statements, so shared-reader locking is unsafe. Advance the owned SymSpell revision for batched delete insertion and cover concurrent root lookups under TSAN.

Signed-off-by: trvon <me@trevon.dev>

* fix(vector): make mutations atomic

Preserve stored vectors on replacement and commit failures, reject cross-thread writes during explicit transactions, and restore exact reranking for quantized-primary PQ rows.

* fix(search)!: surface SymSpell write errors

Propagate persistent dictionary failures through metadata ingestion, roll back failed batches, and make clear remove stored terms.

BREAKING CHANGE: SymSpellSearch mutations now return Result values.

* feat(topology): formalize candidate expansion

* feat(vector): add coordinate-aware embeddings

* feat(topology): preserve protected relation cover

* feat(search): add graph candidate rescue

* feat(daemon): expose retrieval hydration telemetry

* feat(xplan): add rescue and hydration gates

* refactor(content): dedupe media handlers

Reuse shared graph presenter helpers and centralize repeated audio/video/archive buffer processing, validation, lifecycle, and stats helper logic.

* feat(lifecycle): prune stale corpus data

Keep cleanup preview-first while retention stays opt-in and long prune work no longer holds IPC responses open.

* feat(search): add topology candidate rescue

Preserve high-value routed candidates across vector backends while keeping exact rerank diagnostics explicit.

* feat(search): expose evidence paths

* docs: define mobile host contract

* formal: model bounded candidate rescue

* fix(prune): inspect extensionless signatures

* fix(prune): tolerate stale extensions

---------

Signed-off-by: trvon <me@trevon.dev>
2026-07-24 19:00:22 -04:00

241 lines
8.1 KiB
C++

#pragma once
#include "tests/benchmarks/beir_loader.h"
#include <yams/vector/vector_types.h>
#include <cstddef>
#include <cstdint>
#include <filesystem>
#include <iosfwd>
#include <map>
#include <optional>
#include <random>
#include <set>
#include <string>
#include <vector>
#include <nlohmann/json.hpp>
namespace yams::bench {
bool benchmarkSearchIndexReusable(yams::vector::VectorSearchEngine engine,
bool backendPersistedReusable);
bool benchmarkSearchIndexQueryReady(yams::vector::VectorSearchEngine engine,
bool persistedIndexReusable, bool preparedThisRun);
void writeTopologyDisabledEmbeddingSelection(std::ostream& out, bool topologyDisabled);
std::optional<double> processPeakRssMb() noexcept;
struct TestQuery {
std::string query;
std::set<std::string> relevantFiles;
std::set<std::string> relevantDocIds;
std::map<std::string, int> relevanceGrades;
bool useDocIds = false;
};
enum class SyntheticCorpusMode { Generic, CommunityGraph, HardLexical };
struct CorpusGenerator {
std::vector<std::string> topics = {"authentication", "database", "network", "parsing",
"encryption", "testing", "logging", "storage"};
std::vector<std::string> terms = {"user", "password", "token", "session",
"connection", "request", "response", "cache",
"error", "file", "data"};
std::filesystem::path corpusDir;
std::vector<std::string> createdFiles;
std::mt19937 rng{42};
SyntheticCorpusMode mode = SyntheticCorpusMode::Generic;
explicit CorpusGenerator(const std::filesystem::path& dir,
SyntheticCorpusMode corpusMode = SyntheticCorpusMode::Generic);
void generateDocuments(int count);
std::vector<TestQuery> generateQueries(int numQueries);
};
struct LexicalTopologyDocument {
std::string documentHash;
std::string text;
};
struct LexicalTopologyNeighbor {
std::string sourceHash;
std::string targetHash;
float score{0.0F};
};
struct GraphTopologyFeature {
std::int64_t nodeId = 0;
float weight = 0.0F;
};
struct GraphTopologyDocument {
std::string documentHash;
std::vector<GraphTopologyFeature> relatedNodes;
};
struct KeyphraseTopologyDocument {
std::string documentHash;
std::string text;
};
enum class BenchmarkTopologySignalKind {
LexicalCohesion,
LexicalDisruption,
Keyphrase,
Entity,
Ontology,
Structure,
Embedding,
};
std::string topologySignalKindName(BenchmarkTopologySignalKind kind);
struct BenchmarkTopologyGateConfig {
float minSignalWeight = 1.0F;
std::size_t maxSignalDocumentFrequency = 64;
float minNeighborScore = 0.0F;
std::size_t maxComponentDocs = 64;
};
struct BenchmarkTopologySignal {
std::string documentHash;
std::string featureId;
BenchmarkTopologySignalKind kind = BenchmarkTopologySignalKind::LexicalCohesion;
float weight = 0.0F;
std::size_t documentFrequency = 0;
};
struct BenchmarkTopologyNeighborObservation {
std::string sourceHash;
std::string targetHash;
std::string sourceSegmentId;
std::string targetSegmentId;
float score = 0.0F;
};
struct BenchmarkTopologyEdgeWitness {
std::string sourceHash;
std::string targetHash;
std::string sourceSegmentId;
std::string targetSegmentId;
float score = 0.0F;
std::string featureId;
BenchmarkTopologySignalKind kind = BenchmarkTopologySignalKind::LexicalCohesion;
float sourceWeight = 0.0F;
float targetWeight = 0.0F;
std::size_t documentFrequency = 0;
bool reciprocal = false;
bool signalGatePassed = false;
};
struct BenchmarkTopologyComponentSummary {
std::string rootHash;
std::vector<std::string> memberHashes;
};
struct BenchmarkTopologyConstructionCertificate {
std::string sourceName;
BenchmarkTopologyGateConfig gates;
std::vector<BenchmarkTopologySignal> signals;
std::vector<BenchmarkTopologyNeighborObservation> observations;
std::vector<BenchmarkTopologyEdgeWitness> edgeWitnesses;
std::vector<BenchmarkTopologyComponentSummary> components;
};
struct BenchmarkTopologyValidationResult {
bool ok = true;
std::vector<std::string> errors;
};
BenchmarkTopologyValidationResult validateTopologyConstructionCertificate(
const BenchmarkTopologyConstructionCertificate& certificate);
nlohmann::json
topologyConstructionCertificateToJson(const BenchmarkTopologyConstructionCertificate& certificate,
const BenchmarkTopologyValidationResult& validation = {});
std::vector<LexicalTopologyNeighbor>
buildLexicalTopologyNeighbors(const std::vector<LexicalTopologyDocument>& documents,
std::size_t topK, float minScore, bool reciprocalOnly = true,
BenchmarkTopologyConstructionCertificate* certificate = nullptr);
std::vector<LexicalTopologyNeighbor>
buildGraphTopologyNeighbors(const std::vector<GraphTopologyDocument>& documents, std::size_t topK,
float minScore, bool reciprocalOnly = true,
BenchmarkTopologyConstructionCertificate* certificate = nullptr);
std::vector<LexicalTopologyNeighbor> buildKeyphraseTopologyNeighbors(
const std::vector<KeyphraseTopologyDocument>& documents, std::size_t topK, float minScore,
std::size_t maxFeaturesPerDocument = 16, std::size_t maxFeatureDocumentFrequency = 64,
bool reciprocalOnly = true, BenchmarkTopologyConstructionCertificate* certificate = nullptr);
std::vector<LexicalTopologyNeighbor> buildSegmentKeyphraseTopologyNeighbors(
const std::vector<KeyphraseTopologyDocument>& documents, std::size_t topK, float minScore,
std::size_t segmentTokenBudget = 192, std::size_t segmentTokenOverlap = 32,
std::size_t maxFeaturesPerSegment = 12, std::size_t maxFeatureDocumentFrequency = 64,
std::size_t maxComponentDocs = 64, std::size_t maxEdgesPerFeature = 64,
bool reciprocalOnly = true, BenchmarkTopologyConstructionCertificate* certificate = nullptr);
struct BEIRCorpusLoader {
BEIRDataset dataset;
std::filesystem::path corpusDir;
std::map<std::string, std::string> docIdToHash;
BEIRCorpusLoader(const BEIRDataset& ds, const std::filesystem::path& dir);
void writeDocumentsAsFiles();
std::vector<TestQuery> generateTestQueries();
std::vector<std::string> getDocumentPaths() const;
};
yams::Result<BEIRDataset> loadBenchmarkManifestDataset(const std::string& datasetName,
const std::filesystem::path& manifestRoot);
struct RetrievalMetrics {
double mrr = 0.0;
double recallAtK = 0.0;
double precisionAtK = 0.0;
double ndcgAtK = 0.0;
double map = 0.0;
double duplicateRateAtK = 0.0;
int numQueries = 0;
};
struct StageMetricAccumulator {
double totalMRR = 0.0;
double totalRecall = 0.0;
double totalPrecision = 0.0;
double totalNDCG = 0.0;
double totalMAP = 0.0;
std::uint64_t queryCount = 0;
};
struct QueryScoreSample {
int firstRelevantRank = -1;
int numRelevantInTopK = 0;
int numRelevantSeen = 0;
bool hadDuplicateInTopK = false;
double reciprocalRank = 0.0;
double recallAtK = 0.0;
double precisionAtK = 0.0;
double averagePrecision = 0.0;
double ndcgAtK = 0.0;
std::vector<int> retrievedGrades;
std::vector<std::string> duplicateDocIds;
};
std::string canonicalResultDocId(const TestQuery& query, const std::string& fileName);
bool isRelevantDocForQuery(const TestQuery& query, const std::string& docId);
int relevanceGradeForQuery(const TestQuery& query, const std::string& docId);
QueryScoreSample scoreRankedDocIds(const TestQuery& query,
const std::vector<std::string>& rankedDocIds, int k);
void addStageMetricSample(StageMetricAccumulator& acc, const std::vector<std::string>& rankedDocIds,
const TestQuery& query, int k);
RetrievalMetrics finalizeStageMetrics(const StageMetricAccumulator& acc);
nlohmann::json
stageRetrievalMetricsToJson(const std::map<std::string, StageMetricAccumulator>& stageMetrics);
} // namespace yams::bench