Source identity
| Field | Recorded value |
|---|---|
| Source type | GitHub source |
| Original record | Janady13/missionmatch / internal/modules/ai/retrieval.go |
| Date | Not specified in source |
| Repository | Janady13/missionmatch |
| Revision | 6c56e29231ec05735927fa265c97d517499dfeaf |
| Branch | ws6-retrieval |
| Path or archive member | internal/modules/ai/retrieval.go |
| Original SHA-256 | cc3adef2510fb13cd575277b3e8dfa913048e3448bd53b814d51e662679e551f |
| Git blob SHA | 3b087bd51cd7cac1213d9aee3197904b354ec7ae |
| Reading-copy SHA-256 | cc3adef2510fb13cd575277b3e8dfa913048e3448bd53b814d51e662679e551f |
Recovered source transcript
Download complete source text ↓93 lines · 3.5 KB
package ai
import (
"context"
"errors"
"github.com/jackc/pgx/v5"
pgvector "github.com/pgvector/pgvector-go"
"github.com/missionworkforce/missionmatch/internal/platform/database/dbgen"
"github.com/missionworkforce/missionmatch/internal/platform/inference"
"github.com/missionworkforce/missionmatch/internal/platform/tenant"
)
// Hit is one retrieved candidate with its fused relevance score.
type Hit struct {
CandidateID string `json:"candidate_id"`
FullName string `json:"full_name"`
Score float64 `json:"rrf_score"`
}
// HybridSearch is the RAG retrieval leg: it embeds the query through the MEGAMIND plane and runs the
// existing hybrid query that fuses pgvector cosine kNN with full text rank via Reciprocal Rank Fusion
// (docs/AI_RESEARCH.md, pgvector + RRF). It runs RLS scoped as the tenant, so it returns only the
// caller's candidates, and it tunes the HNSW scan per the pgvector 0.8 guidance before the search.
//
// When mission is a non empty mission keyword (from MissionFromQuestion, drawn only from the fixed
// mission_spine vocabulary), retrieval is additionally constrained to candidates whose full_name, headline,
// or location text matches that mission, via the mission scoped query (WS6 Option A). An empty mission runs
// the unscoped query byte for byte as before, so the default path is unchanged. The mission keyword is
// candidate text match only, never a semantic mission tag (those are WS7); the limit is documented in
// docs/AI_RESEARCH.md.
//
// It never touches the eeo schema (the query reads only candidates), and the query string is guarded so
// a demographic reference cannot enter the retrieval path.
func HybridSearch(ctx context.Context, db *tenant.DB, client *inference.Client, query, mission string, limit int) ([]Hit, error) {
if db == nil || client == nil {
return nil, errors.New("ai: retrieval needs a database and an inference client")
}
if err := ForbidEEO(query); err != nil {
return nil, err
}
if limit <= 0 || limit > 100 {
limit = 20
}
emb, err := client.Embed(ctx, []string{query})
if err != nil {
return nil, err
}
if len(emb.Embeddings) != 1 {
return nil, errors.New("ai: query embedding failed")
}
qv := pgvector.NewVector(emb.Embeddings[0])
var hits []Hit
err = db.RunInTenantTx(ctx, func(ctx context.Context, tx pgx.Tx) error {
// pgvector 0.8 tuning: a wider ef_search lifts recall on the filtered (RLS) scan, and relaxed
// iterative scan avoids overfiltering under the tenant predicate (docs/AI_RESEARCH.md, pgvector).
if _, err := tx.Exec(ctx, "SET LOCAL hnsw.ef_search = 100"); err != nil {
return err
}
if _, err := tx.Exec(ctx, "SET LOCAL hnsw.iterative_scan = 'relaxed_order'"); err != nil {
return err
}
q := dbgen.New(tx)
if mission != "" {
rows, err := q.SearchCandidatesHybridMission(ctx, dbgen.SearchCandidatesHybridMissionParams{
QueryVector: qv, RowLimit: int32(limit), TextQuery: query, MissionQuery: mission,
})
if err != nil {
return err
}
hits = make([]Hit, len(rows))
for i, r := range rows {
hits[i] = Hit{CandidateID: r.ID.String(), FullName: r.FullName, Score: r.RrfScore}
}
return nil
}
rows, err := q.SearchCandidatesHybrid(ctx, dbgen.SearchCandidatesHybridParams{
Column1: qv, Limit: int32(limit), WebsearchToTsquery: query,
})
if err != nil {
return err
}
hits = make([]Hit, len(rows))
for i, r := range rows {
hits[i] = Hit{CandidateID: r.ID.String(), FullName: r.FullName, Score: r.RrfScore}
}
return nil
})
return hits, err
}