| name | vector-search |
| description | Implement vector search for knowledge retrieval. Use when adding RAG, semantic search, knowledge base features, or context building for the agent. |
| allowed-tools | Read, Grep, Glob, Edit, Write, Bash |
Vector Search (Upstash Vector)
Context strategy is retrieval-first. The agent uses hybrid search (dense + sparse) to find relevant context.
Upstash Vector Setup
import { Index } from '@upstash/vector'
const index = new Index({
url: process.env.UPSTASH_VECTOR_URL,
token: process.env.UPSTASH_VECTOR_TOKEN,
})
Document Types
Single index with type filters:
interface VectorDocument {
id: string
data: string
metadata: {
type: 'conversation' | 'knowledge' | 'response'
appId: string
category?: MessageCategory
resolution?: 'refund' | 'transfer' | 'info' | 'escalated'
customerSentiment?: 'positive' | 'neutral' | 'negative'
touchCount?: number
resolvedAt?: string
source?: 'docs' | 'faq' | 'policy' | 'canned-response'
title?: string
lastUpdated?: string
trustScore?: number
usageCount?: number
conversationId?: string
}
}
PII Redaction (Required)
Always redact PII before embedding:
function redactPII(text: string, knownNames: string[] = []): string {
let redacted = text
.replace(/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g, '[EMAIL]')
.replace(/(\+?1[-.\s]?)?\(?[0-9]{3}\)?[-.\s]?[0-9]{3}[-.\s]?[0-9]{4}/g, '[PHONE]')
.replace(/\b(?:\d{4}[-\s]?){3}\d{4}\b/g, '[CARD]')
if (knownNames.length > 0) {
redacted = redacted.replace(
new RegExp(knownNames.map(escapeRegex).join('|'), 'gi'),
'[NAME]'
)
}
return redacted
}
Upsert Pattern
await index.upsert([{
id: conversationId,
data: redactPII(messageText),
metadata: {
type: 'conversation',
appId,
category,
resolution
}
}])
Agent Context Building
Build context with parallel queries:
async function buildAgentContext(message: string, appId: string) {
const query = redactPII(message)
const [similarTickets, knowledge, goodResponses] = await Promise.all([
index.query({
data: query,
topK: 3,
filter: `appId = '${appId}' AND type = 'conversation' AND resolution != 'escalated'`,
includeData: true,
includeMetadata: true,
}),
index.query({
data: query,
topK: 5,
filter: `appId = '${appId}' AND type = 'knowledge'`,
includeData: true,
includeMetadata: true,
}),
index.query({
data: query,
topK: 3,
filter: `appId = '${appId}' AND type = 'response' AND trustScore > 0.85`,
includeData: true,
includeMetadata: ,
}),
])
{ similarTickets, knowledge, goodResponses }
}
Optional: Cohere Rerank
For higher quality results, use reranking:
async function buildAgentContextWithRerank(message: string, appId: string) {
const query = redactPII(message)
const [denseResults, sparseResults] = await Promise.all([
index.query({ data: query, queryMode: 'DENSE', topK: 30, filter: `appId = '${appId}'`, includeData: true }),
index.query({ data: query, queryMode: 'SPARSE', topK: 30, filter: `appId = '${appId}'`, includeData: true }),
])
const candidates = dedupeById([...denseResults, ...sparseResults])
const reranked = await cohere.rerank({
model: 'rerank-v4.0-pro',
query: message,
documents: candidates.map(c => c.data),
topN: ,
})
results = reranked..( candidates[r.])
{
: results.( r.. === ).(, ),
: results.( r.. === ).(, ),
: results.( r.. === ).(, ),
}
}
File Locations
- Vector client:
packages/core/src/vector/client.ts
- Context building:
packages/core/src/vector/context.ts
- PII redaction:
packages/core/src/vector/redact.ts
Reference Docs
For full details, see:
docs/support-app-prd/71-vector-search.md
docs/support-app-prd/72-context-strategy.md