Foundgine

Retrieval Strategies (Fuzzy / Full-Text / Search / Graph / Vector)

Files: Tests/Retrieval/*.cs. Provider under test: Foundgine.Providers.Storage.Sql.Retrieval.PostgresRetrievalCandidateSource (see also src/csharp/Foundgine.Providers.Storage.Sql/README.md).

The concept: retrieval feeds grounding, grounding feeds planning

Where 03-Ambiguity-And-Grounding.md covers what happens once candidate interpretations exist, this file covers where those candidates come from for a real, non-toy schema: PostgresRetrievalCandidateSource implements several distinct RetrievalStrategy values, each backed by a different PostgreSQL capability, each tested against fixtures shaped like this sample’s actual Supplier/Product tables.

The five strategies, what each needs, and how to opt in

Strategy Backed by Opt-in required Test file
Fuzzy pg_trgm (trigram similarity) FOUNDGINE_POSTGRES_CONNECTION only — enabled by default on any Postgres with the extension SupplyChainFuzzyAndFullTextRetrievalTests.cs
FullText Native Postgres full-text search FOUNDGINE_POSTGRES_CONNECTION only SupplyChainFuzzyAndFullTextRetrievalTests.cs
Search pg_search (ParadeDB BM25) FOUNDGINE_POSTGRES_CONNECTION and FOUNDGINE_POSTGRES_PGSEARCH=1 SupplyChainSearchRetrievalTests.cs
GraphSimilarity Apache AGE FOUNDGINE_POSTGRES_CONNECTION and FOUNDGINE_POSTGRES_AGE=1 SupplyChainGraphSimilarityRetrievalTests.cs
Vector (reserved for a future pgvector provider) n/a — always short-circuits SupplyChainRetrievalCapabilityTests.cs
Relational (documented no-op) n/a SupplyChainRetrievalCapabilityTests.cs

Why two strategies need a second, explicit opt-in on top of the connection string: pg_search and Apache AGE are not installed on a vanilla PostgreSQL image — unlike pg_trgm and full-text search, which ship with core Postgres. Gating them behind their own environment variables means CI and a typical local Postgres stay green (those tests are skipped, not failed) while anyone who has actually installed the extension gets full coverage. This is the same “opt-in, not opt-out” philosophy the claims validator uses for unrecognized claim keys (see 01-Claims-And-Authorization.md) — absence of a capability degrades gracefully instead of failing loud in an environment that never claimed to support it.

Fuzzy vs. full-text: two different questions, not two strengths of the same one

It’s tempting to think of Fuzzy and FullText as “loose” vs. “strict” matching. They’re actually answering different questions:

Search (BM25) — why it’s a separate strategy from FullText

Native Postgres full-text search and ParadeDB’s pg_search both operate over text, but BM25 ranking (term frequency, inverse document frequency, field- length normalization) produces materially different relevance ordering for free-text queries like "metal fabrication" against a Supplier.Name field — SupplyChainSearchRetrievalTests.cs’s test name (Search_retrieval_ranks_suppliers_by_bm25_relevance) is explicit that ranking quality, not just match/no-match, is what this strategy is tested for.

GraphSimilarity — a genuinely separate index, not a mirror of foreign keys

The doc comment on SupplyChainGraphSimilarityRetrievalTests.cs makes a subtle point worth restating: the AGE graph models “two suppliers are neighbor-similar when they both connect to the same purchase-order vertex” — even though, relationally, a single purchase order has exactly one owning supplier (a real foreign key, one-to-many). The AGE graph is a separate, purpose-built retrieval index shaped for a co-sourcing/risk similarity question (“which suppliers are structurally similar to this one?”), not a graph-shaped copy of the relational schema. This is worth internalizing if you’re adding a new graph retrieval case: the question is “what similarity structure does this business question need,” not “how do I represent my foreign keys as edges.”

Vector and Relational — the two strategies that never touch Postgres

SupplyChainRetrievalCapabilityTests.cs is the one file in this set that needs no real database at all — it builds a syntactically valid but unreachable connection string (Host=127.0.0.1;Port=1;...;Timeout=1) and asserts that every strategy either:

This is provider-_wiring_ coverage, deliberately separate from the provider-_behavior_ coverage in the other four files: it proves the gating logic itself is correct (a disabled strategy never reaches PostgreSQL to find out it’s disabled) without needing any infrastructure to do so.

find_top_supplier_overdue_orders’s own fallback: a lighter cousin of PostgresRetrievalCandidateSource

Everything above exercises PostgresRetrievalCandidateSource directly against SupplyChainSemanticModel.Metadata and a throwaway fixture schema — it’s provider-conformance testing for IApproximateCandidateSource, not a capability. Semantic/Api/Mcp/Program.cs’s SupplyChainExecutionService implements the same three practical strategies (Fuzzy, FullText, Search) a second time, independently, scoped to one real question: does the supplierName a caller passed to find_top_supplier_overdue_orders approximately match a supplier in the requested state?

It’s deliberately not routed through PostgresRetrievalCandidateSource. That type resolves against _metadata.GetEntity/ResolveField and the generated SupplyChainSemanticModel.Metadata catalog — the right shape when retrieval has to be generic across arbitrary entities/fields chosen at grounding time. find_top_supplier_overdue_orders already knows, at compile time, that it’s matching Supplier.Name scoped to one state — so SupplyChainExecutionService.TryApproximateSupplierMatchAsync is three small, direct SQL queries (TryFuzzyAsync, TryFullTextAsync, TrySearchAsync) against the real suppliers table, run in that order, stopping at the first strategy that returns anything:

Order Strategy SQL shape Gate
1 Fuzzy similarity(supplier_name, @name) / supplier_name % @name always on — Database/Program.cs provisions CREATE EXTENSION IF NOT EXISTS pg_trgm and a gin_trgm_ops index on suppliers.supplier_name as part of the sample’s own schema
2 FullText ts_rank_cd(to_tsvector(...), websearch_to_tsquery(...)) always on — native Postgres, no extension needed
3 Search pdb.score(supplier_id) / supplier_name \|\|\| @name FOUNDGINE_POSTGRES_PGSEARCH=1, same gate as SupplyChainSearchRetrievalTests.cs above

Two things carried over deliberately from PostgresRetrievalCandidateSource and from the tie-break case earlier in this walkthrough:


Previous: 03-Ambiguity-And-Grounding.md · Next: 05-Adversarial-Security-Testing.md