diff --git a/content/Guides/vector-db.mdx b/content/Guides/vector-db.mdx
index ffa5173c..900be0d4 100644
--- a/content/Guides/vector-db.mdx
+++ b/content/Guides/vector-db.mdx
@@ -44,13 +44,16 @@ Vector storage is created automatically when your agent first calls `context.vec
## Vector Storage API
+For complete API documentation, see:
+- [JavaScript SDK Vector API Reference](/SDKs/javascript/api-reference#vector-storage)
+- [Python SDK Vector API Reference](/SDKs/python/api-reference#vector-storage)
+
### Upserting Documents
The `upsert` operation inserts new documents or updates existing ones. You can provide either text (which gets automatically converted to embeddings) or pre-computed embeddings.
-**SDK Differences:**
-- **JavaScript SDK**: No key field required, search returns `distance` (0 = perfect match)
-- **Python SDK**: Requires `key` field for each document, search returns `similarity` (1.0 = perfect match)
+**SDK Requirements:**
+- **Both SDKs**: Require a `key` field for each document
**Idempotent Behavior:**
The upsert operation is idempotent - upserting with an existing key updates the existing vector rather than creating a duplicate. The same internal vector ID is reused, ensuring your vector storage remains clean and efficient.
@@ -62,10 +65,12 @@ The upsert operation is idempotent - upserting with an existing key updates the
const ids = await context.vector.upsert(
'knowledge-base',
{
+ key: 'doc-1',
document: 'Agentuity is an agent-native cloud platform',
metadata: { category: 'platform', source: 'docs' }
},
{
+ key: 'doc-2',
document: 'Vector storage enables semantic search capabilities',
metadata: { category: 'features', source: 'docs' }
}
@@ -75,6 +80,7 @@ const ids = await context.vector.upsert(
const embeddingIds = await context.vector.upsert(
'custom-embeddings',
{
+ key: 'embedding-1',
embeddings: [0.1, 0.2, 0.3, 0.4],
metadata: { id: 'doc-1', type: 'custom' }
}
@@ -130,7 +136,7 @@ const results = await context.vector.search('knowledge-base', {
// Process results
results.forEach(result => {
console.log(`Found: ${result.metadata.source}`);
- console.log(`Similarity: ${1 - result.distance}`);
+ console.log(`Similarity: ${result.similarity}`);
});
```
@@ -158,20 +164,25 @@ for result in results:
- `similarity` (optional): Minimum similarity threshold (0.0-1.0)
- `metadata` (optional): Filter results by metadata key-value pairs
+**Search Results:**
+- **Both SDKs**: Return results with `similarity` field (1.0 = perfect match, 0.0 = no match)
+- **Note**: The JavaScript SDK also returns a `distance` field for backward compatibility; prefer `similarity`
+
### Deleting Vectors
-Remove specific vectors from storage using their IDs (JavaScript) or keys (Python).
+Remove specific vectors from storage using their keys.
```javascript
// JavaScript/TypeScript
// Delete single vector
-const deletedCount = await context.vector.delete('knowledge-base', 'vector-id-1');
+const deletedCount = await context.vector.delete('knowledge-base', 'doc-1');
+
// Delete multiple vectors
const bulkDeleteCount = await context.vector.delete(
'knowledge-base',
- 'id-1', 'id-2', 'id-3'
+ 'doc-1', 'doc-2', 'doc-3'
);
```
@@ -185,6 +196,304 @@ count = await context.vector.delete("knowledge-base", "doc_1")
```
+## Practical Examples
+
+For more code examples, see:
+- [JavaScript SDK Examples](/SDKs/javascript/examples#vector-storage-usage)
+- [Python SDK Examples](/SDKs/python/examples#vector-storage)
+
+### Building a Simple RAG System
+
+This example demonstrates a complete Retrieval-Augmented Generation (RAG) pattern - searching for relevant context and using it to generate informed responses.
+
+ {
+ const { question } = await request.data.json();
+
+ try {
+ // 1. Search for relevant context (top 5 results)
+ const searchResults = await context.vector.search('knowledge-base', {
+ query: question,
+ limit: 5,
+ similarity: 0.7
+ });
+
+ // 2. Handle no results gracefully
+ if (searchResults.length === 0) {
+ return response.json({
+ answer: "I couldn't find relevant information to answer your question.",
+ sources: []
+ });
+ }
+
+ // 3. Assemble context from search results (defensive handling)
+ const contextTexts = searchResults.map(result =>
+ result.metadata?.content ?? result.metadata?.text ?? ''
+ );
+ const assembledContext = contextTexts.join('\\n\\n');
+
+ // 4. Generate response using context (example with AI Gateway)
+ const prompt = \`Answer the question based on the following context:
+
+Context: \${assembledContext}
+
+Question: \${question}
+
+Answer:\`;
+
+ // Use your preferred LLM here (OpenAI, Anthropic, etc.)
+ const llmResponse = await generateAnswer(prompt);
+
+ // 5. Return answer with sources
+ return response.json({
+ answer: llmResponse,
+ sources: searchResults.map(r => ({
+ id: r.id,
+ key: r.key,
+ title: r.metadata?.title,
+ similarity: r.similarity
+ }))
+ });
+
+ } catch (error) {
+ context.logger.error('RAG query failed:', error);
+ return response.json({
+ error: 'Failed to process your question',
+ details: error.message
+ });
+ }
+};
+
+export default handler;`} py={`# Python - Simple RAG implementation
+from agentuity import AgentRequest, AgentResponse, AgentContext
+
+async def run(request: AgentRequest, response: AgentResponse, context: AgentContext):
+ data = await request.data.json()
+ question = data.get("question")
+
+ try:
+ # 1. Search for relevant context (top 5 results)
+ search_results = await context.vector.search(
+ "knowledge-base",
+ query=question,
+ limit=5,
+ similarity=0.7
+ )
+
+ # 2. Handle no results gracefully
+ if not search_results:
+ return response.json({
+ "answer": "I couldn't find relevant information to answer your question.",
+ "sources": []
+ })
+
+ # 3. Assemble context from search results
+ context_texts = [
+ result.metadata.get("content", result.metadata.get("text", ""))
+ for result in search_results
+ ]
+ assembled_context = "\\n\\n".join(context_texts)
+
+ # 4. Generate response using context (example with AI Gateway)
+ prompt = f"""Answer the question based on the following context:
+
+Context: {assembled_context}
+
+Question: {question}
+
+Answer:"""
+
+ # Use your preferred LLM here (OpenAI, Anthropic, etc.)
+ llm_response = await generate_answer(prompt)
+
+ # 5. Return answer with sources
+ return response.json({
+ "answer": llm_response,
+ "sources": [
+ {
+ "id": result.id,
+ "key": result.key,
+ "title": result.metadata.get("title"),
+ "similarity": result.similarity
+ }
+ for result in search_results
+ ]
+ })
+
+ except Exception as e:
+ context.logger.error(f"RAG query failed: {e}")
+ return response.json({
+ "error": "Failed to process your question",
+ "details": str(e)
+ })`} />
+
+**Key Points:**
+- **Semantic search** finds relevant documents based on meaning, not keywords
+- **Similarity threshold** of 0.7 balances relevance with recall
+- **Context assembly** combines multiple sources for comprehensive answers
+- **Error handling** ensures graceful failures with helpful messages
+- **Source attribution** provides transparency about where information came from
+
+### Semantic Search with Metadata Filtering
+
+This example shows how to combine semantic similarity with metadata filters for precise results - like finding products that match both meaning and business criteria.
+
+ {
+ const { query, maxPrice, category, inStock } = await request.data.json();
+
+ try {
+ // Build metadata filters based on criteria
+ const metadataFilters = {};
+ if (category) metadataFilters.category = category;
+ if (inStock !== undefined) metadataFilters.inStock = inStock;
+
+ // Search with semantic similarity + metadata filters
+ const searchResults = await context.vector.search('products', {
+ query,
+ limit: 10,
+ similarity: 0.65, // Lower threshold for broader results
+ metadata: metadataFilters
+ });
+
+ // Post-process: Apply price filter and sort by relevance
+ const filteredResults = searchResults
+ .filter(result => !maxPrice || result.metadata.price <= maxPrice)
+ .map(result => ({
+ ...result.metadata,
+ similarity: result.similarity
+ }))
+ .sort((a, b) => b.similarity - a.similarity);
+
+ return response.json({
+ query,
+ filters: { maxPrice, category, inStock },
+ resultCount: filteredResults.length,
+ products: filteredResults.slice(0, 5) // Top 5 results
+ });
+
+ } catch (error) {
+ context.logger.error('Product search failed:', error);
+ return response.json({
+ error: 'Search failed',
+ products: []
+ });
+ }
+};
+
+export default handler;`} py={`# Python - Product search with filters
+from agentuity import AgentRequest, AgentResponse, AgentContext
+
+async def run(request: AgentRequest, response: AgentResponse, context: AgentContext):
+ data = await request.data.json()
+ query = data.get("query")
+ max_price = data.get("maxPrice")
+ category = data.get("category")
+ in_stock = data.get("inStock")
+
+ try:
+ # Build metadata filters based on criteria
+ metadata_filters = {}
+ if category:
+ metadata_filters["category"] = category
+ if in_stock is not None:
+ metadata_filters["inStock"] = in_stock
+
+ # Search with semantic similarity + metadata filters
+ search_results = await context.vector.search(
+ "products",
+ query=query,
+ limit=10,
+ similarity=0.65, # Lower threshold for broader results
+ metadata=metadata_filters
+ )
+
+ # Post-process: Apply price filter and sort by relevance
+ filtered_results = []
+ for result in search_results:
+ # Apply price filter
+ if max_price and result.metadata.get("price", 0) > max_price:
+ continue
+
+ product = dict(result.metadata)
+ product["similarity"] = result.similarity
+ filtered_results.append(product)
+
+ # Sort by similarity score
+ filtered_results.sort(key=lambda x: x["similarity"], reverse=True)
+
+ return response.json({
+ "query": query,
+ "filters": {"maxPrice": max_price, "category": category, "inStock": in_stock},
+ "resultCount": len(filtered_results),
+ "products": filtered_results[:5] # Top 5 results
+ })
+
+ except Exception as e:
+ context.logger.error(f"Product search failed: {e}")
+ return response.json({
+ "error": "Search failed",
+ "products": []
+ })`} />
+
+**Key Techniques:**
+- **Metadata filters** are applied at the vector search level for efficiency
+- **Post-processing** handles filters that can't be done at search time (like price ranges)
+- **Lower similarity threshold** (0.65) catches more potential matches when using strict filters
+
+## Common Pitfalls & Solutions
+
+### Empty Search Results
+**Problem**: Your search returns empty results even though relevant data exists.
+
+**Solutions**:
+- **Lower the similarity threshold**: Start at 0.5 and increase gradually
+- **Check your metadata filters**: They use exact matching, not fuzzy matching
+- **Verify document format**: Ensure documents were upserted with text content
+
+```javascript
+// Adaptive threshold example
+let results = await context.vector.search('kb', {
+ query,
+ similarity: 0.8
+});
+
+if (results.length === 0) {
+ // Try again with lower threshold
+ results = await context.vector.search('kb', {
+ query,
+ similarity: 0.5
+ });
+}
+```
+
+### Duplicate Documents
+**Problem**: Same content appears multiple times in search results.
+
+**Solution**: Vector upsert is idempotent when using the same key:
+- Always use consistent `key` values for your documents
+- Upserting with an existing key updates the vector rather than creating a duplicate
+- The same internal vector ID is reused, keeping your storage clean
+
+### Performance Issues
+**Problem**: Vector operations take too long.
+
+**Solutions**:
+- **Batch operations**: Upsert 100-500 documents at once, not one by one
+- **Limit search results**: Use `limit: 10` instead of retrieving all matches
+- **Optimize metadata**: Keep metadata objects small and focused
+
+### Irrelevant Search Results
+**Problem**: Search returns irrelevant or unexpected documents.
+
+**Solutions**:
+- **Check similarity scores**: Results with similarity < 0.7 may be poor matches
+- **Review metadata filters**: Remember they're AND conditions, not OR
+- **Verify embeddings**: Ensure consistent text preprocessing before upserting
## Best Practices
diff --git a/content/SDKs/javascript/api-reference.mdx b/content/SDKs/javascript/api-reference.mdx
index b30e071a..20b96e56 100644
--- a/content/SDKs/javascript/api-reference.mdx
+++ b/content/SDKs/javascript/api-reference.mdx
@@ -196,7 +196,7 @@ Inserts or updates vectors in the vector storage.
**Parameters**
- `name`: The name of the vector storage
-- `documents`: One or more documents to upsert, each with either embeddings or text
+- `documents`: One or more documents to upsert. Each document must include a unique `key` and either embeddings or text
**Return Value**
@@ -208,15 +208,15 @@ Returns a Promise that resolves to an array of string IDs for the upserted vecto
// Upsert documents with text
const ids = await context.vector.upsert(
'product-descriptions',
- { document: 'Ergonomic office chair with lumbar support', metadata: { category: 'furniture' } },
- { document: 'Wireless noise-cancelling headphones', metadata: { category: 'electronics' } }
+ { key: 'chair-001', document: 'Ergonomic office chair with lumbar support', metadata: { category: 'furniture' } },
+ { key: 'headphones-001', document: 'Wireless noise-cancelling headphones', metadata: { category: 'electronics' } }
);
// Upsert documents with embeddings
const ids2 = await context.vector.upsert(
'product-embeddings',
- { embeddings: [0.1, 0.2, 0.3, 0.4], metadata: { productId: '123' } },
- { embeddings: [0.5, 0.6, 0.7, 0.8], metadata: { productId: '456' } }
+ { key: 'embed-123', embeddings: [0.1, 0.2, 0.3, 0.4], metadata: { productId: '123' } },
+ { key: 'embed-456', embeddings: [0.5, 0.6, 0.7, 0.8], metadata: { productId: '456' } }
);
```
diff --git a/content/SDKs/javascript/examples/index.mdx b/content/SDKs/javascript/examples/index.mdx
index 7ce11b6f..be235fc7 100644
--- a/content/SDKs/javascript/examples/index.mdx
+++ b/content/SDKs/javascript/examples/index.mdx
@@ -109,8 +109,14 @@ const handler: AgentHandler = async (request, response, context) => {
return response.json({ error: 'No products to index' });
}
- // Prepare documents for vector storage
+ // Validate that all products have IDs
+ if (products.some(p => !p.id)) {
+ return response.json({ error: 'All products must have a non-empty id' });
+ }
+
+ // Prepare documents for vector storage with string normalization
const documents = products.map(product => ({
+ key: String(product.id),
document: product.description,
metadata: {
id: product.id,
@@ -142,10 +148,12 @@ const handler: AgentHandler = async (request, response, context) => {
similarity: 0.7
});
- // Format results
+ // Format results with id, key, and similarity
const formattedResults = results.map(result => ({
+ id: result.id,
+ key: result.key,
...result.metadata,
- similarity: 1 - result.distance // Convert distance to similarity score
+ similarity: result.similarity
}));
return response.json({