SkillAgentSearch skills...

vector-search

AI-powered chat application built with Next.js, Convex, AI SDK, and modern web technologies. Features real-time messaging, PWA support, documentation with Fumadocs, and enterprise-ready architecture with Turborepo and Ultracite.

Install / Use

npx skills add tonyoconnell/anubis.chat

Installs into whichever agent you are using.

About this skill
📐

Cursor Rules

Cursor IDE rules (v2)

Quality Score

70/100

Supported Platforms

Cursor

Our assessment of vector-search

vector-search scores 70/100 on our quality scale, 377th of 393 Communication skills we index.

Its Cursor Rules is 25 KB long, well organised into 17 sections with 9 code examples: a thorough specification that gives an agent plenty to work with.

It has no GitHub stars yet, so there is no community track record; judge it on its content.

Substance
30/30
Structure
20/20
Description
15/15
Adoption
0/20
Freshness
5/15

Maintenance, license and trust

  • We could not determine when the repository was last updated.
  • Our last check on 2026-10-18 found the source still online.
  • No license is declared. By default that means all rights are reserved: you can read it, but reusing or redistributing it is not clearly permitted. Ask the author before building on it commercially.
  • Its trust signals score 68/100, with 3 cautions from licensing, adoption, age or documentation. These come from repository metadata, not a code audit — read the skill file before letting an agent act on it.

Safety scan

No issues found

Our scan of the whole file found no instruction hijacking, hidden characters, credential access, data exfiltration or destructive commands. An AI review of the same text found nothing harmful.

AI review by kimi-k2.7-code on 2026-09-24. Automated pattern scan on 2026-09-24. It catches known dangerous patterns, not every risk — read a skill before letting an agent act on it.

vector-search compared with similar skills

All 4 of these similar skills score higher than vector-search; compare them before choosing.

SkillScoreStarsUpdatedFormat
vector-search (this skill)by tonyoconnell700—Cursor Rules
pptxby anthropics100177.9k44d agoSKILL.md
slack-gif-creatorby ComposioHQ10078.5k49d agoSKILL.md
prospectingby coreyhaines3110057.7k6d agoSKILL.md
api-onboardingby sickn3310048.9k6d agoSKILL.md

Frequently asked questions

How do I install vector-search?
Run npx skills add tonyoconnell/anubis.chat. The install tabs above show the steps for each supported agent.
Which AI agents does vector-search work with?
It is written for Cursor, as a Cursor Rules file. Other agents that read the same format can often use it too.
Is vector-search safe to use?
Our scan of the whole file found no instruction hijacking, hidden characters, credential access, data exfiltration or destructive commands. An AI review of the same text found nothing harmful. It declares no license and scores 68/100 on trust signals. Skills are instructions an agent will follow, so read the file before installing it and do not approve commands you do not understand.
Is vector-search still maintained?
We could not determine when the repository was last updated.

category: backend subcategory: vector-search tags: [qdrant, vectors, embeddings, semantic-search, rag] cursor: context_window: 8192 temperature: 0.5 max_tokens: 4096 model_preference: ["auto"] relations: imports: ["../ai-rag/embeddings.mdc", "./database-schema.mdc"] exports: ["vector-operations", "similarity-search", "hybrid-search"] references: ["./convex-patterns.mdc", "./api-design.mdc"]

Vector Search & Qdrant Integration - v1.9+ Best Practices

Core Architecture Principles

Hybrid Search Strategy: Combine dense vectors (semantic) with sparse vectors (keyword) for optimal results Multi-Tenant Isolation: Strict wallet-based data separation using collection filters Performance Optimization: 97% RAM reduction through quantization and smart indexing Scalable Infrastructure: Horizontal scaling through sharding and replication

Qdrant Client Configuration

Production-Ready Setup

// lib/qdrant-client.ts
import { QdrantClient } from '@qdrant/js-client-rest';

export class QdrantService {
  private client: QdrantClient;
  
  constructor() {
    this.client = new QdrantClient({
      url: process.env.QDRANT_URL!,
      apiKey: process.env.QDRANT_API_KEY,
      timeout: 30000, // 30 second timeout
      retries: 3,
    });
  }
  
  // Collection management with proper indexing
  async initializeCollections() {
    const collections = [
      {
        name: 'message_embeddings',
        config: {
          vectors: {
            size: 1536, // OpenAI embedding dimension
            distance: 'Cosine',
            on_disk: true, // Store vectors on disk for large datasets
          },
          optimizers_config: {
            deleted_threshold: 0.2,
            vacuum_min_vector_number: 1000,
            default_segment_number: 0,
            max_segment_size: 20000,
            memmap_threshold: 10000,
            indexing_threshold: 20000,
            payload_indexing_threshold: 10000,
          },
          replication_factor: 2, // High availability
          write_consistency_factor: 1,
          quantization_config: {
            scalar: {
              type: 'int8',
              quantile: 0.99,
              always_ram: true,
            },
          },
        },
      },
      {
        name: 'document_embeddings',
        config: {
          vectors: {
            dense: {
              size: 1536,
              distance: 'Cosine',
              on_disk: true,
            },
            sparse: {
              distance: 'Dot',
              on_disk: false, // Keep sparse vectors in RAM for speed
            },
          },
          // Hybrid search configuration
          quantization_config: {
            binary: {
              always_ram: true,
            },
          },
        },
      },
    ];
    
    for (const { name, config } of collections) {
      try {
        await this.client.getCollection(name);
      } catch {
        await this.client.createCollection(name, config);
        await this.createPayloadIndexes(name);
      }
    }
  }
  
  // Create indexes for wallet isolation and filtering
  private async createPayloadIndexes(collectionName: string) {
    const indexes = [
      { field: 'wallet_address', schema_type: 'keyword' },
      { field: 'chat_id', schema_type: 'keyword' },
      { field: 'message_role', schema_type: 'keyword' },
      { field: 'timestamp', schema_type: 'integer' },
      { field: 'document_type', schema_type: 'keyword' },
      { field: 'is_active', schema_type: 'bool' },
    ];
    
    for (const index of indexes) {
      await this.client.createPayloadIndex(collectionName, index);
    }
  }
}

Message Embedding Operations

Store Chat Message Embeddings

// ✅ Wallet-isolated message embedding storage
export const storeMessageEmbedding = action({
  args: {
    messageId: v.id("messages"),
    walletAddress: v.string(),
    embedding: v.array(v.number()),
    sparseVector: v.optional(v.object({
      indices: v.array(v.number()),
      values: v.array(v.number()),
    })),
  },
  handler: async (ctx, args) => {
    try {
      // Validate message ownership
      const message = await ctx.runQuery(api.messages.getMessage, {
        messageId: args.messageId,
        walletAddress: args.walletAddress,
      });
      
      if (!message) {
        throw new Error("Message not found or access denied");
      }
      
      const qdrant = new QdrantService();
      
      // Prepare point data with comprehensive metadata
      const pointData = {
        id: args.messageId,
        vector: args.sparseVector 
          ? {
              dense: args.embedding,
              sparse: args.sparseVector,
            }
          : args.embedding,
        payload: {
          wallet_address: args.walletAddress,
          chat_id: message.chatId,
          message_role: message.role,
          content: message.content.substring(0, 1000), // Truncate for indexing
          timestamp: message.timestamp,
          token_count: mess…[redacted] || 0,
          is_active: true,
          created_at: Date.now(),
        },
      };
      
      // Store in Qdrant with retry logic
      await qdrant.client.upsert('message_embeddings', {
        wait: true,
        points: [pointData],
      });
      
      // Update Convex record with embedding
      await ctx.runMutation(api.messages.updateEmbedding, {
        messageId: args.messageId,
        embedding: args.embedding,
      });
      
      return { success: true };
      
    } catch (error) {
      console.error("Failed to store message embedding:", error);
      throw new Error("Failed to store embedding");
    }
  },
});

Semantic Search for Chat Context

// ✅ Context-aware semantic search for RAG
export const searchSimilarMessages = action({
  args: {
    walletAddress: v.string(),
    query: v.string(),
    chatId: v.optional(v.id("chats")),
    limit: v.optional(v.number()),
    threshold: v.optional(v.number()),
  },
  handler: async (ctx, { walletAddress, query, chatId, limit = 10, threshold = 0.7 }) => {
    try {
      // Generate query embedding
      const queryEmbedding = await generateEmbedding(query);
      
      const qdrant = new QdrantService();
      
      // Build search filters for wallet isolation
      const filters = {
        must: [
          { key: 'wallet_address', match: { value: walletAddress } },
          { key: 'is_active', match: { value: true } },
        ],
      };
      
      // Add chat-specific filter if provided
      if (chatId) {
        filters.must.push({
          key: 'chat_id',
          match: { value: chatId },
        });
      }
      
      // Perform vector search
      const searchResult = await qdrant.client.search('message_embeddings', {
        vector: queryEmbedding,
        filter: filters,
        limit,
        score_threshold: threshold,
        with_payload: true,
        with_vectors: false, // Don't return vectors to save bandwidth
      });
      
      // Enrich results with full message data from Convex
      const enrichedResults = await Promise.all(
        searchResult.map(async (result) => {
          const message = await ctx.runQuery(api.messages.getMessage, {
            messageId: result.id as Id<"messages">,
            walletAddress,
          });
          
          return {
            messageId: result.id,
            score: result.score,
            content: message?.content || result.payload?.content,
            role: message?.role || result.payload?.message_role,
            timestamp: message?.timestamp || result.payload?.timestamp,
            chatId: message?.chatId || result.payload?.chat_id,
          };
        })
      );
      
      return {
        results: enrichedResults,
        query,
        totalFound: searchResult.length,
      };
      
    } catch (error) {
      console.error("Semantic search failed:", error);
      throw new Error("Search failed");
    }
  },
});

Document RAG Integration

Document Chunk Embeddings

// ✅ Store document chunks with hybrid vectors
export const storeDocumentChunks = action({
  args: {
    documentId: v.id("documents"),
    walletAddress: v.string(),
    chunks: v.array(v.object({
      content: v.string(),
      chunkIndex: v.number(),
      startOffset: v.number(),
      endOffset: v.number(),
      metadata: v.optional(v.any()),
    })),
  },
  handler: async (ctx, args) => {
    try {
      // Validate document ownership
      const document = await ctx.runQuery(api.documents.getDocument, {
        documentId: args.documentId,
        walletAddress: args.walletAddress,
      });
      
      if (!document) {
        throw new Error("Document not found or access denied");
      }
      
      const qdrant = new QdrantService();
      const batchSize = 100; // Process in batches
      
      for (let i = 0; i < args.chunks.length; i += batchSize) {
        const batch = args.chunks.slice(i, i + batchSize);
        
        // Generate embeddings for batch
        const embeddings = await Promise.all(
          batch.map(chunk => generateHybridEmbedding(chunk.content))
        );
        
        // Prepare points for Qdrant
        const points = batch.map((chunk, idx) => {
          const chunkId = `${args.documentId}_chunk_${chunk.chunkIndex}`;
          
          return {
            id: chunkId,
            vector: {
              dense: embeddings[idx].dense,
              sparse: embeddings[idx].sparse,
            },
            payload: {
              wallet_address: args.walletAddress,
              document_id: args.documentId,
              chunk_index: chunk.chunkIndex,
              document_type: document.type,
              document_title: document.title,
              content: chunk.content,
              start_offset: chunk.startOffset,
              end_offset: chunk.endOffset,
              metadata: chunk.metadata || {},
              timestamp: Date.now(),
              is_active: true,
            },
          };
        });
        
        // Batch upsert to Qdrant
        await qdrant.client.upsert('document_embeddings', {
          wait: true,
          points,
        });
        
        // Store chunks in Convex
        const chunkIds = await Promise.all(
          batch.map(chunk => ctx.runMutation(api.chunks.createChunk, {
            documentId: args.documentId,
            walletAddress: args.walletAddress,
            content: chunk.content,
            chunkIndex: chunk.chunkIndex,
            startOffset: chunk.startOffset,
            endOffset: chunk.endOffset,
            embedding: embeddings[batch.indexOf(chunk)].dense,
          }))
        );
      }
      
      return { success: true, chunksProcessed: args.chunks.length };
      
    } catch (error) {
      console.error("Failed to store document chunks:", error);
      throw new Error("Failed to process document chunks");
    }
  },
});

Hybrid RAG Search

// ✅ Advanced hybrid search combining dense + sparse vectors
export const hybridRAGSearch = action({
  args: {
    walletAddress: v.string(),
    query: v.string(),
    documentTypes: v.optional(v.array(v.string())),
    limit: v.optional(v.number()),
    alpha: v.optional(v.number()), // Dense/sparse weight balance
  },
  handler: async (ctx, { walletAddress, query, documentTypes, limit = 20, alpha = 0.7 }) => {
    try {
      const qdrant = new QdrantService();
      
      // Generate hybrid query vectors
      const hybridQuery = await generateHybridEmbedding(query);
      
      // Build comprehensive filters
      const filters = {
        must: [
          { key: 'wallet_address', match: { value: walletAddress } },
          { key: 'is_active', match: { value: true } },
        ],
      };
      
      // Add document type filters if specified
      if (documentTypes && documentTypes.length > 0) {
        filters.must.push({
          k

Truncated for display — read the full file on GitHub.

Related Skills

View on GitHub
GitHub Stars0
CategoryCommunication
UpdatedNaNy ago
Forks0

Trust signals

68/100

From repository metadata: license, adoption, age and documentation. Not a code audit — see the Safety scan above for what the skill file itself contains.

2 medium1 low