#pragma once #include "tests/benchmarks/beir_loader.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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 processPeakRssMb() noexcept; struct TestQuery { std::string query; std::set relevantFiles; std::set relevantDocIds; std::map relevanceGrades; bool useDocIds = false; }; enum class SyntheticCorpusMode { Generic, CommunityGraph, HardLexical }; struct CorpusGenerator { std::vector topics = {"authentication", "database", "network", "parsing", "encryption", "testing", "logging", "storage"}; std::vector terms = {"user", "password", "token", "session", "connection", "request", "response", "cache", "error", "file", "data"}; std::filesystem::path corpusDir; std::vector 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 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 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 memberHashes; }; struct BenchmarkTopologyConstructionCertificate { std::string sourceName; BenchmarkTopologyGateConfig gates; std::vector signals; std::vector observations; std::vector edgeWitnesses; std::vector components; }; struct BenchmarkTopologyValidationResult { bool ok = true; std::vector errors; }; BenchmarkTopologyValidationResult validateTopologyConstructionCertificate( const BenchmarkTopologyConstructionCertificate& certificate); nlohmann::json topologyConstructionCertificateToJson(const BenchmarkTopologyConstructionCertificate& certificate, const BenchmarkTopologyValidationResult& validation = {}); std::vector buildLexicalTopologyNeighbors(const std::vector& documents, std::size_t topK, float minScore, bool reciprocalOnly = true, BenchmarkTopologyConstructionCertificate* certificate = nullptr); std::vector buildGraphTopologyNeighbors(const std::vector& documents, std::size_t topK, float minScore, bool reciprocalOnly = true, BenchmarkTopologyConstructionCertificate* certificate = nullptr); std::vector buildKeyphraseTopologyNeighbors( const std::vector& 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 buildSegmentKeyphraseTopologyNeighbors( const std::vector& 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); /// How benchmark seeding turns per-document neighbor lists into `semantic_neighbor` KG edges. /// MutualPairs writes both directions of every mutual pair and drops one-way neighbors. /// DirectedKnn also writes each one-way neighbor as a single directed edge, as the daemon does /// (`GraphComponent` treats a one-way edge as an ordinary non-mutual kNN edge). The connected /// topology engine keeps only reciprocal edges in its components, so one-way edges are its /// only source of cross-cluster roads. enum class SemanticNeighborSeeding { MutualPairs, DirectedKnn }; struct SemanticNeighborSeedEdge { std::string sourceHash; std::string targetHash; float weight{0.0F}; bool mutual{false}; }; /// `neighborScores[src][dst]` is dst's score in src's neighbor list. A mutual pair gets weight /// max(weightFloor, mean of both scores) in both directions; a one-way edge (DirectedKnn only) /// gets max(weightFloor, its score). Self entries are skipped. Output is sorted by /// (sourceHash, targetHash). std::vector buildSemanticNeighborSeedEdges( const std::unordered_map>& neighborScores, float weightFloor, SemanticNeighborSeeding mode); struct BEIRCorpusLoader { BEIRDataset dataset; std::filesystem::path corpusDir; std::map docIdToHash; BEIRCorpusLoader(const BEIRDataset& ds, const std::filesystem::path& dir); void writeDocumentsAsFiles(); std::vector generateTestQueries(); std::vector getDocumentPaths() const; /// File path `writeDocumentsAsFiles` uses for a corpus document id. std::filesystem::path documentPath(const std::string& docId) const; }; /// Directory ingestion cannot carry per-file metadata, so after the corpus files are ingested /// copy each `BEIRDocument::metadata` onto the stored document matched by exact file path /// (`BEIRCorpusLoader::documentPath`). Documents without metadata are skipped. Returns the /// number of documents updated; `missingDocuments`, when given, counts documents with metadata /// that are not in `repo`. Re-running overwrites the same keys, so warm caches may repeat it. std::size_t applyBenchmarkDocumentMetadata(yams::metadata::IMetadataRepository& repo, const BEIRCorpusLoader& loader, std::size_t* missingDocuments = nullptr); yams::Result 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 retrievedGrades; std::vector 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& rankedDocIds, int k); void addStageMetricSample(StageMetricAccumulator& acc, const std::vector& rankedDocIds, const TestQuery& query, int k); RetrievalMetrics finalizeStageMetrics(const StageMetricAccumulator& acc); nlohmann::json stageRetrievalMetricsToJson(const std::map& stageMetrics); } // namespace yams::bench