Vector Databases in Ductape
Vector databases have become essential infrastructure for modern AI applications, powering everything from semantic search to recommendation systems and RAG (Retrieval-Augmented Generation) pipelines. Ductape provides a unified, provider-agnostic interface for working with vector databases, allowing you to switch between providers without changing your application code.
Why Vector Databases Matter
Traditional databases excel at exact matches and structured queries, but they struggle with semantic similarity. When you need to find "documents similar to this one" or "products related to this search query," you need vector databases.
Vector databases store high-dimensional embeddings—numerical representations of text, images, or other data—and enable lightning-fast similarity searches across millions of vectors.
Supported Providers
Ductape supports multiple vector database providers out of the box:
| Provider | Best For | Key Features |
|---|---|---|
| Pinecone | Production workloads | Fully managed, scales automatically |
| Qdrant | Self-hosted deployments | Open source, rich filtering |
| Weaviate | Hybrid search | Built-in ML models, GraphQL API |
| In-Memory | Development & testing | Zero configuration, instant setup |
Getting Started
1. Define Your Vector Database
First, create a vector database configuration in your Ductape product:
- TypeScript
- Java
- Go
- .NET
import Ductape from '@ductape/sdk';
const ductape = new Ductape({
workspace_id: 'your-workspace-id',
private_key: 'your-private-key',
product: 'my-product',
env: 'prd',
});
await ductape.product.init('my-product');
// Create a vector database configuration
await ductape.vector.create({
tag: 'document-embeddings',
name: 'Document Embeddings',
type: 'pinecone',
dimensions: 1536, // OpenAI ada-002 embedding size
metric: 'cosine',
envs: [
{
slug: 'prd',
apiKey: '{{PINECONE_API_KEY}}', // Use secrets
endpoint: 'https://my-index.pinecone.io',
},
],
});
import app.ductape.sdk.Ductape;
import app.ductape.sdk.core.EnvType;
import app.ductape.sdk.core.RequestContext;
Map<String, Object> ductape = new Ductape(Map.of(
"workspace_id", "your-workspace-id",
"private_key", "your-private-key",
"product", "my-product",
"env", "prd"
));
ductape.product.init('my-product');
// Create a vector database configuration
ductape.vector.create(Map.of(
"tag", "document-embeddings",
"name", "Document Embeddings",
"type", "pinecone",
"dimensions", 1536, // OpenAI ada-002 embedding size
"metric", "cosine",
envs: [
Map.of(
"slug", "prd",
"apiKey", "Map.of(Map.of(PINECONE_API_KEY))", // Use secrets
"endpoint", "https://my-index.pinecone.io"
),
]
));
import (
"context"
"github.com/ductape/ductape/sdk/go/core"
ductapesdk "github.com/ductape/ductape/sdk/go/ductape"
)
ductape := new Ductape({
"workspace_id": "your-workspace-id",
"private_key": "your-private-key",
"product": "my-product",
"env": "prd",
});
client.product.init('my-product');
// Create a vector database configuration
client.vector.create({
"tag": "document-embeddings",
"name": "Document Embeddings",
"type": "pinecone",
"dimensions": 1536, // OpenAI ada-002 embedding size
"metric": "cosine",
envs: [
{
"slug": "prd",
"apiKey": "{{PINECONE_API_KEY}}", // Use secrets
"endpoint": "https://my-index.pinecone.io",
},
],
});
using Ductape.Sdk;
using Ductape.Sdk.Core;
var ductape = new Ductape({
["workspace_id"] = "your-workspace-id",
["private_key"] = "your-private-key",
["product"] = "my-product",
["env"] = "prd",
});
await ductape.product.init('my-product');
// Create a vector database configuration
await ductape.vector.create({
["tag"] = "document-embeddings",
["name"] = "Document Embeddings",
["type"] = "pinecone",
["dimensions"] = 1536, // OpenAI ada-002 embedding size
["metric"] = "cosine",
envs: [
{
["slug"] = "prd",
["apiKey"] = "{{PINECONE_API_KEY}}", // Use secrets
["endpoint"] = "https://my-index.pinecone.io",
},
],
});
2. Connect and Use
Once configured, connecting and querying is straightforward:
- TypeScript
- Java
- Go
- .NET
// Upsert vectors
await ductape.vector.upsert({
tag: 'document-embeddings',
vectors: [
{
id: 'doc-1',
values: embedding, // Your embedding array
metadata: {
title: 'Introduction to Machine Learning',
category: 'tech',
author: 'Jane Doe',
},
},
],
namespace: 'articles',
});
// Query for similar vectors
const results = await ductape.vector.query({
tag: 'document-embeddings',
vector: queryEmbedding,
topK: 10,
namespace: 'articles',
filter: {
field: 'category',
operator: '$eq',
value: 'tech',
},
includeMetadata: true,
});
console.log(results.matches);
// [
// { id: 'doc-1', score: 0.95, metadata: { title: '...', ... } },
// { id: 'doc-7', score: 0.89, metadata: { title: '...', ... } },
// ...
// ]
// Upsert vectors
ductape.vector.upsert(Map.of(
"tag", "document-embeddings",
vectors: [
Map.of(
"id", "doc-1",
values: embedding, // Your embedding array
metadata: Map.of(
"title", "Introduction to Machine Learning",
"category", "tech",
"author", "Jane Doe"
)
),
],
"namespace", "articles"
));
// Query for similar vectors
Map<String, Object> results = ductape.vectors().query(Map<String, Object>.of(
"tag", "document-embeddings",
vector: queryEmbedding,
"topK", 10,
"namespace", "articles",
filter: Map.of(
"field", "category",
"operator", "$eq",
"value", "tech"
),
"includeMetadata", true
));
System.out.println(results.matches);
// [
// Map.of( "id", "doc-1", "score", 0.95, metadata: Map.of( "title", "...", ... ) ),
// Map.of( "id", "doc-7", "score", 0.89, metadata: Map.of( "title", "...", ... ) ),
// ...
// ]
import "context"
// Upsert vectors
client.vector.upsert({
"tag": "document-embeddings",
vectors: [
{
"id": "doc-1",
values: embedding, // Your embedding array
metadata: {
"title": "Introduction to Machine Learning",
"category": "tech",
"author": "Jane Doe",
},
},
],
"namespace": "articles",
});
// Query for similar vectors
results := client.VectorAPI.Query(ctx, map[string]any{
"tag": "document-embeddings",
vector: queryEmbedding,
"topK": 10,
"namespace": "articles",
filter: {
"field": "category",
"operator": "$eq",
"value": "tech",
},
"includeMetadata": true,
});
fmt.Println(results.matches);
// [
// { "id": "doc-1", "score": 0.95, metadata: { "title": "...", ... } },
// { "id": "doc-7", "score": 0.89, metadata: { "title": "...", ... } },
// ...
// ]
// Upsert vectors
await ductape.vector.upsert({
["tag"] = "document-embeddings",
vectors: [
{
["id"] = "doc-1",
values: embedding, // Your embedding array
metadata: {
["title"] = "Introduction to Machine Learning",
["category"] = "tech",
["author"] = "Jane Doe",
},
},
],
["namespace"] = "articles",
});
// Query for similar vectors
var results = await ductape.Vector.Query(new Dictionary<string, object?>
{
["tag"] = "document-embeddings",
vector: queryEmbedding,
["topK"] = 10,
["namespace"] = "articles",
filter: {
["field"] = "category",
["operator"] = "$eq",
["value"] = "tech",
},
["includeMetadata"] = true,
});
Console.WriteLine(results.matches);
// [
// { ["id"] = "doc-1", ["score"] = 0.95, metadata: { ["title"] = "...", ... } },
// { ["id"] = "doc-7", ["score"] = 0.89, metadata: { ["title"] = "...", ... } },
// ...
// ]
Key Features
Environment-Based Configuration
Like all Ductape resources, vector databases support environment-specific configurations. Use different providers or credentials for development, staging, and production:
- TypeScript
- Java
- Go
- .NET
envs: [
{ slug: 'dev', type: 'memory' }, // In-memory for development
{ slug: 'stg', type: 'qdrant', endpoint: 'http://qdrant-staging:6333' },
{ slug: 'prd', type: 'pinecone', apiKey: '{{PINECONE_API_KEY}}' },
]
envs: [
Map.of( "slug", "dev", "type", "memory" ), // In-memory for development
Map.of( "slug", "stg", "type", "qdrant", "endpoint", "http://qdrant-"staging", 6333" ),
Map.of( "slug", "prd", "type", "pinecone", "apiKey", "Map.of(Map.of(PINECONE_API_KEY))" ),
]
envs: [
{ "slug": "dev", "type": "memory" }, // In-memory for development
{ "slug": "stg", "type": "qdrant", "endpoint": "http://qdrant-"staging": 6333" },
{ "slug": "prd", "type": "pinecone", "apiKey": "{{PINECONE_API_KEY}}" },
]
envs: [
{ ["slug"] = "dev", ["type"] = "memory" }, // In-memory for development
{ ["slug"] = "stg", ["type"] = "qdrant", ["endpoint"] = "http://qdrant-["staging"] = 6333" },
{ ["slug"] = "prd", ["type"] = "pinecone", ["apiKey"] = "{{PINECONE_API_KEY}}" },
]
Namespace Isolation
Organize vectors into namespaces for logical separation:
- TypeScript
- Java
- Go
- .NET
// User-specific embeddings
await ductape.vector.upsert({
...options,
namespace: `user-${userId}`,
vectors: userDocuments,
});
// Query only within a user's namespace
const results = await ductape.vector.query({
...options,
namespace: `user-${userId}`,
vector: queryVector,
});
// User-specific embeddings
ductape.vector.upsert(Map.of(
...options,
namespace: `user-$Map.of(userId)`,
vectors: userDocuments
));
// Query only within a user's namespace
Map<String, Object> results = ductape.vectors().query(Map<String, Object>.of(
...options,
namespace: `user-$Map.of(userId)`,
vector: queryVector
));
import "context"
// User-specific embeddings
client.vector.upsert({
...options,
namespace: `user-${userId}`,
vectors: userDocuments,
});
// Query only within a user's namespace
results := client.VectorAPI.Query(ctx, map[string]any{
...options,
namespace: `user-${userId}`,
vector: queryVector,
});
// User-specific embeddings
await ductape.vector.upsert({
...options,
namespace: `user-${userId}`,
vectors: userDocuments,
});
// Query only within a user's namespace
var results = await ductape.Vector.Query(new Dictionary<string, object?>
{
...options,
namespace: `user-${userId}`,
vector: queryVector,
});
Metadata Filtering
Combine vector similarity with metadata filters for precise results:
- TypeScript
- Java
- Go
- .NET
const results = await ductape.vector.query({
tag: 'products',
vector: queryEmbedding,
topK: 20,
filter: {
operator: '$and',
filters: [
{ field: 'category', operator: '$eq', value: 'electronics' },
{ field: 'price', operator: '$lte', value: 500 },
{ field: 'in_stock', operator: '$eq', value: true },
],
},
});
Map<String, Object> results = ductape.vectors().query(Map<String, Object>.of(
"tag", "products",
vector: queryEmbedding,
"topK", 20,
filter: Map.of(
"operator", "$and",
filters: [
Map.of( "field", "category", "operator", "$eq", "value", "electronics" ),
Map.of( "field", "price", "operator", "$lte", "value", 500 ),
Map.of( "field", "in_stock", "operator", "$eq", "value", true ),
]
)
));
import "context"
results := client.VectorAPI.Query(ctx, map[string]any{
"tag": "products",
vector: queryEmbedding,
"topK": 20,
filter: {
"operator": "$and",
filters: [
{ "field": "category", "operator": "$eq", "value": "electronics" },
{ "field": "price", "operator": "$lte", "value": 500 },
{ "field": "in_stock", "operator": "$eq", "value": true },
],
},
});
var results = await ductape.Vector.Query(new Dictionary<string, object?>
{
["tag"] = "products",
vector: queryEmbedding,
["topK"] = 20,
filter: {
["operator"] = "$and",
filters: [
{ ["field"] = "category", ["operator"] = "$eq", ["value"] = "electronics" },
{ ["field"] = "price", ["operator"] = "$lte", ["value"] = 500 },
{ ["field"] = "in_stock", ["operator"] = "$eq", ["value"] = true },
],
},
});
Caching Integration
Enable caching for repeated queries to reduce latency and costs:
- TypeScript
- Java
- Go
- .NET
const results = await ductape.vector.query({
tag: 'document-embeddings',
vector: queryEmbedding,
topK: 10,
cache: 'vector-query-cache', // Cache tag
});
Map<String, Object> results = ductape.vectors().query(Map<String, Object>.of(
"tag", "document-embeddings",
vector: queryEmbedding,
"topK", 10,
"cache", "vector-query-cache", // Cache tag
));
import "context"
results := client.VectorAPI.Query(ctx, map[string]any{
"tag": "document-embeddings",
vector: queryEmbedding,
"topK": 10,
"cache": "vector-query-cache", // Cache tag
});
var results = await ductape.Vector.Query(new Dictionary<string, object?>
{
["tag"] = "document-embeddings",
vector: queryEmbedding,
["topK"] = 10,
["cache"] = "vector-query-cache", // Cache tag
});
Built-in Logging
All vector operations are automatically logged, providing visibility into:
- Query patterns and performance
- Upsert volumes
- Error rates
- Cost tracking
View logs in the Ductape dashboard or query them programmatically:
- TypeScript
- Java
- Go
- .NET
const logs = await ductape.logs.fetch({
component: 'product',
type: 'vector',
});
Map<String, Object> logs = ductape.logs.fetch(Map.of(
"component", "product",
"type", "vector"
));
logs := client.logs.fetch({
"component": "product",
"type": "vector",
});
var logs = await ductape.logs.fetch({
["component"] = "product",
["type"] = "vector",
});
Common Use Cases
Semantic Search
Build search experiences that understand meaning, not just keywords:
- TypeScript
- Java
- Go
- .NET
async function semanticSearch(query: string) {
// Generate embedding for the search query
const queryEmbedding = await generateEmbedding(query);
// Find similar documents
const results = await ductape.vector.query({
tag: 'documents',
vector: queryEmbedding,
topK: 10,
includeMetadata: true,
});
return results.matches.map(match => ({
id: match.id,
title: match.metadata?.title,
score: match.score,
}));
}
async function semanticSearch(query: string) Map.of(
// Generate embedding for the search query
Map<String, Object> queryEmbedding = generateEmbedding(query);
// Find similar documents
Map<String, Object> results = ductape.vectors().query(Map<String, Object>.of(
"tag", "documents",
vector: queryEmbedding,
"topK", 10,
"includeMetadata", true
));
return results.matches.map(match => (Map.of(
id: match.id,
title: match.metadata?.title,
score: match.score
)));
)
import "context"
async function semanticSearch(query: string) {
// Generate embedding for the search query
queryEmbedding := generateEmbedding(query);
// Find similar documents
results := client.VectorAPI.Query(ctx, map[string]any{
"tag": "documents",
vector: queryEmbedding,
"topK": 10,
"includeMetadata": true,
});
return results.matches.map(match => ({
id: match.id,
title: match.metadata?.title,
score: match.score,
}));
}
async function semanticSearch(query: string) {
// Generate embedding for the search query
var queryEmbedding = await generateEmbedding(query);
// Find similar documents
var results = await ductape.Vector.Query(new Dictionary<string, object?>
{
["tag"] = "documents",
vector: queryEmbedding,
["topK"] = 10,
["includeMetadata"] = true,
});
return results.matches.map(match => ({
id: match.id,
title: match.metadata?.title,
score: match.score,
}));
}
RAG (Retrieval-Augmented Generation)
Enhance LLM responses with relevant context:
- TypeScript
- Java
- Go
- .NET
async function ragQuery(userQuestion: string) {
// 1. Find relevant context
const queryEmbedding = await generateEmbedding(userQuestion);
const context = await ductape.vector.query({
tag: 'knowledge-base',
vector: queryEmbedding,
topK: 5,
includeMetadata: true,
});
// 2. Build prompt with context
const contextText = context.matches
.map(m => m.metadata?.content)
.join('\n\n');
// 3. Generate response with LLM
const response = await llm.complete({
prompt: `Context:\n${contextText}\n\nQuestion: ${userQuestion}\n\nAnswer:`,
});
return response;
}
async function ragQuery(userQuestion: string) Map.of(
// 1. Find relevant context
Map<String, Object> queryEmbedding = generateEmbedding(userQuestion);
Map<String, Object> context = ductape.vectors().query(Map<String, Object>.of(
"tag", "knowledge-base",
vector: queryEmbedding,
"topK", 5,
"includeMetadata", true
));
// 2. Build prompt with context
Map<String, Object> contextText = context.matches
.map(m => m.metadata?.content)
.join('\n\n');
// 3. Generate response with LLM
Map<String, Object> response = llm.complete(Map.of(
prompt: `Context:\n$Map.of(contextText)\n\nQuestion: $Map.of(userQuestion)\n\nAnswer:`
));
return response;
)
import "context"
async function ragQuery(userQuestion: string) {
// 1. Find relevant context
queryEmbedding := generateEmbedding(userQuestion);
context := client.VectorAPI.Query(ctx, map[string]any{
"tag": "knowledge-base",
vector: queryEmbedding,
"topK": 5,
"includeMetadata": true,
});
// 2. Build prompt with context
contextText := context.matches
.map(m => m.metadata?.content)
.join('\n\n');
// 3. Generate response with LLM
response := llm.complete({
prompt: `Context:\n${contextText}\n\nQuestion: ${userQuestion}\n\nAnswer:`,
});
return response;
}
async function ragQuery(userQuestion: string) {
// 1. Find relevant context
var queryEmbedding = await generateEmbedding(userQuestion);
var context = await ductape.Vector.Query(new Dictionary<string, object?>
{
["tag"] = "knowledge-base",
vector: queryEmbedding,
["topK"] = 5,
["includeMetadata"] = true,
});
// 2. Build prompt with context
var contextText = context.matches
.map(m => m.metadata?.content)
.join('\n\n');
// 3. Generate response with LLM
var response = await llm.complete({
prompt: `Context:\n${contextText}\n\nQuestion: ${userQuestion}\n\nAnswer:`,
});
return response;
}
Recommendation Systems
Power personalized recommendations:
- TypeScript
- Java
- Go
- .NET
async function getRecommendations(userId: string, productId: string) {
// Get the product's embedding
const product = await ductape.vector.fetchVectors({
tag: 'products',
ids: [productId],
});
// Find similar products
const similar = await ductape.vector.query({
tag: 'products',
vector: product.vectors[productId].values,
topK: 10,
filter: {
field: 'id',
operator: '$ne',
value: productId, // Exclude the original
},
});
return similar.matches;
}
async function getRecommendations(userId: string, productId: string) Map.of(
// Get the product's embedding
Map<String, Object> product = ductape.vector.fetchVectors(Map.of(
"tag", "products",
ids: [productId]
));
// Find similar products
Map<String, Object> similar = ductape.vectors().query(Map<String, Object>.of(
"tag", "products",
vector: product.vectors[productId].values,
"topK", 10,
filter: Map.of(
"field", "id",
"operator", "$ne",
value: productId, // Exclude the original
)
));
return similar.matches;
)
import "context"
async function getRecommendations(userId: string, productId: string) {
// Get the product's embedding
product := client.vector.fetchVectors({
"tag": "products",
ids: [productId],
});
// Find similar products
similar := client.VectorAPI.Query(ctx, map[string]any{
"tag": "products",
vector: product.vectors[productId].values,
"topK": 10,
filter: {
"field": "id",
"operator": "$ne",
value: productId, // Exclude the original
},
});
return similar.matches;
}
async function getRecommendations(userId: string, productId: string) {
// Get the product's embedding
var product = await ductape.vector.fetchVectors({
["tag"] = "products",
ids: [productId],
});
// Find similar products
var similar = await ductape.Vector.Query(new Dictionary<string, object?>
{
["tag"] = "products",
vector: product.vectors[productId].values,
["topK"] = 10,
filter: {
["field"] = "id",
["operator"] = "$ne",
value: productId, // Exclude the original
},
});
return similar.matches;
}
Best Practices
1. Choose the Right Dimensions
Match your embedding dimensions to your model:
- OpenAI
text-embedding-ada-002: 1536 dimensions - OpenAI
text-embedding-3-small: 1536 dimensions - OpenAI
text-embedding-3-large: 3072 dimensions - Cohere
embed-english-v3.0: 1024 dimensions
2. Use Meaningful IDs
Use deterministic, meaningful IDs for vectors to enable easy updates:
- TypeScript
- Java
- Go
- .NET
// Good: Deterministic ID based on content
const id = `doc-${documentId}-chunk-${chunkIndex}`;
// Avoid: Random UUIDs make updates difficult
const id = crypto.randomUUID();
// Good: Deterministic ID based on content
Map<String, Object> id = `doc-$Map.of(documentId)-chunk-$Map.of(chunkIndex)`;
// Avoid: Random UUIDs make updates difficult
Map<String, Object> id = crypto.randomUUID();
// Good: Deterministic ID based on content
id := `doc-${documentId}-chunk-${chunkIndex}`;
// Avoid: Random UUIDs make updates difficult
id := crypto.randomUUID();
// Good: Deterministic ID based on content
var id = `doc-${documentId}-chunk-${chunkIndex}`;
// Avoid: Random UUIDs make updates difficult
var id = crypto.randomUUID();
3. Batch Operations
For bulk operations, use batch methods to improve performance:
- TypeScript
- Java
- Go
- .NET
// Batch upsert
await ductape.vector.upsert({
...options,
vectors: documents.map(doc => ({
id: doc.id,
values: doc.embedding,
metadata: doc.metadata,
})),
});
// Batch upsert
ductape.vector.upsert(Map.of(
...options,
vectors: documents.map(doc => (Map.of(
id: doc.id,
values: doc.embedding,
metadata: doc.metadata
)))
));
// Batch upsert
client.vector.upsert({
...options,
vectors: documents.map(doc => ({
id: doc.id,
values: doc.embedding,
metadata: doc.metadata,
})),
});
// Batch upsert
await ductape.vector.upsert({
...options,
vectors: documents.map(doc => ({
id: doc.id,
values: doc.embedding,
metadata: doc.metadata,
})),
});
4. Monitor and Optimize
Use Ductape's logging to monitor:
- Query latency (aim for < 100ms p95)
- Result quality (track click-through rates)
- Index size and costs
Conclusion
Ductape's vector database module provides a powerful, unified interface for building AI-powered applications. By abstracting away provider-specific details, you can focus on building features while maintaining the flexibility to switch providers as your needs evolve.
Whether you're building semantic search, RAG pipelines, or recommendation systems, Ductape's vector capabilities give you the tools you need to succeed.