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.chatInstalls into whichever agent you are using.
Cursor Rules
Cursor IDE rules (v2)
Quality Score
Category
CommunicationSupported Platforms
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.
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 foundOur 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.
| Skill | Score | Stars | Updated | Format |
|---|---|---|---|---|
| vector-search (this skill)by tonyoconnell | 70 | 0 | — | Cursor Rules |
| pptxby anthropics | 100 | 177.9k | 44d ago | SKILL.md |
| slack-gif-creatorby ComposioHQ | 100 | 78.5k | 49d ago | SKILL.md |
| prospectingby coreyhaines31 | 100 | 57.7k | 6d ago | SKILL.md |
| api-onboardingby sickn33 | 100 | 48.9k | 6d ago | SKILL.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.
Skill content
View source on GitHubcategory: 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
pptx
177.9kUse this skill any time a .pptx or .potx file is involved in any way — as input, output, or both. This includes: creating slide decks, pitch decks, or presentations; reading, parsing, or extracting text from any .pptx or .potx file (even if the extracted content will be used elsewhere, like in an em…
slack-gif-creator
78.5kToolkit for creating animated GIFs optimized for Slack, with validators for size constraints and composable animation primitives. This skill applies when users request animated GIFs or emoji animations for Slack from descriptions like "make me a GIF for Slack of X doing Y".
prospecting
57.7kWhen the user wants to find, qualify, and build a list of prospects to reach out to, across B2B SaaS, general B2B, or local small businesses.
api-onboarding
48.9kReduce time-to-first-API-call (TTFAC) by optimizing every step of the developer onboarding journey. This skill covers authentication simplification, sandbox environments, interactive documentation, and identifying and eliminating common failure points.
Trust signals
From repository metadata: license, adoption, age and documentation. Not a code audit — see the Safety scan above for what the skill file itself contains.
