vector-search

SKILL.md

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  // The text content (PII-redacted)
  metadata: {
    type: 'conversation' | 'knowledge' | 'response'
    appId: string

    // Conversation metadata
    category?: MessageCategory
    resolution?: 'refund' | 'transfer' | 'info' | 'escalated'
    customerSentiment?: 'positive' | 'neutral' | 'negative'
    touchCount?: number
    resolvedAt?: string

    // Knowledge metadata
    source?: 'docs' | 'faq' | 'policy' | 'canned-response'
    title?: string
    lastUpdated?: string

    // Response metadata
    trustScore?: number
    usageCount?: number
    conversationId?: string
  }
}

PII Redaction (Required)

Always redact PII before embedding:

function redactPII(text: string, knownNames: string[] = []): string {
  let redacted = text
    // Email
    .replace(/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g, '[EMAIL]')
    // Phone
    .replace(/(\+?1[-.\s]?)?\(?[0-9]{3}\)?[-.\s]?[0-9]{3}[-.\s]?[0-9]{4}/g, '[PHONE]')
    // Credit card
    .replace(/\b(?:\d{4}[-\s]?){3}\d{4}\b/g, '[CARD]')

  // Known names
  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([
    // Similar resolved tickets
    index.query({
      data: query,
      topK: 3,
      filter: `appId = '${appId}' AND type = 'conversation' AND resolution != 'escalated'`,
      includeData: true,
      includeMetadata: true,
    }),

    // Knowledge base articles
    index.query({
      data: query,
      topK: 5,
      filter: `appId = '${appId}' AND type = 'knowledge'`,
      includeData: true,
      includeMetadata: true,
    }),

    // High-trust responses
    index.query({
      data: query,
      topK: 3,
      filter: `appId = '${appId}' AND type = 'response' AND trustScore > 0.85`,
      includeData: true,
      includeMetadata: true,
    }),
  ])

  return { similarTickets, knowledge, goodResponses }
}

Optional: Cohere Rerank

For higher quality results, use reranking:

async function buildAgentContextWithRerank(message: string, appId: string) {
  const query = redactPII(message)

  // Get candidates from both dense and sparse search
  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])

  // Rerank with Cohere
  const reranked = await cohere.rerank({
    model: 'rerank-v4.0-pro',
    query: message,
    documents: candidates.map(c => c.data),
    topN: 10,
  })

  const results = reranked.results.map(r => candidates[r.index])
  return {
    similarTickets: results.filter(r => r.metadata.type === 'conversation').slice(0, 3),
    knowledge: results.filter(r => r.metadata.type === 'knowledge').slice(0, 5),
    goodResponses: results.filter(r => r.metadata.type === 'response').slice(0, 3),
  }
}

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
Weekly Installs
2
First Seen
Feb 28, 2026
Installed on
opencode2
gemini-cli2
codebuddy2
github-copilot2
codex2
kimi-cli2