Query 在指定命名空间内执行向量检索。
(ctx context.Context, q vector.Query)
| 125 | |
| 126 | // Query 在指定命名空间内执行向量检索。 |
| 127 | func (s *Store) Query(ctx context.Context, q vector.Query) ([]vector.Hit, error) { |
| 128 | if len(q.Vector) != s.dim { |
| 129 | return nil, fmt.Errorf("query vector dimension mismatch: got %d, want %d", len(q.Vector), s.dim) |
| 130 | } |
| 131 | |
| 132 | topK := q.TopK |
| 133 | if topK <= 0 { |
| 134 | topK = 5 |
| 135 | } |
| 136 | |
| 137 | // pgvector 距离越小越相似, 我们将 score 定义为 1 - distance (cosine) 或负的 L2。 |
| 138 | var distanceExpr string |
| 139 | switch s.metric { |
| 140 | case "cosine": |
| 141 | distanceExpr = "embedding <=> $1" // cosine distance |
| 142 | case "l2": |
| 143 | distanceExpr = "embedding <-> $1" // L2 distance |
| 144 | default: |
| 145 | distanceExpr = "embedding <=> $1" |
| 146 | } |
| 147 | |
| 148 | ns := q.Namespace |
| 149 | if ns == "" { |
| 150 | ns = "default" |
| 151 | } |
| 152 | |
| 153 | const baseTmpl = ` |
| 154 | SELECT id, %s AS distance, metadata |
| 155 | FROM %s |
| 156 | WHERE namespace = $2 |
| 157 | ORDER BY distance ASC |
| 158 | LIMIT $3; |
| 159 | ` |
| 160 | query := fmt.Sprintf(baseTmpl, distanceExpr, s.table) |
| 161 | |
| 162 | rows, err := s.pool.Query(ctx, query, float32SliceToPgVector(q.Vector), ns, topK) |
| 163 | if err != nil { |
| 164 | return nil, fmt.Errorf("query vectors: %w", err) |
| 165 | } |
| 166 | defer rows.Close() |
| 167 | |
| 168 | var hits []vector.Hit |
| 169 | for rows.Next() { |
| 170 | var ( |
| 171 | id string |
| 172 | distance float64 |
| 173 | meta map[string]any |
| 174 | ) |
| 175 | if err := rows.Scan(&id, &distance, &meta); err != nil { |
| 176 | return nil, fmt.Errorf("scan row: %w", err) |
| 177 | } |
| 178 | score := distanceToScore(distance, s.metric) |
| 179 | hits = append(hits, vector.Hit{ |
| 180 | ID: id, |
| 181 | Score: score, |
| 182 | Metadata: meta, |
| 183 | }) |
| 184 | } |
nothing calls this directly
no test coverage detected