From 329db6c51bc35b803f09acf917caeaae879f9a16 Mon Sep 17 00:00:00 2001 From: parteeksingh24 Date: Mon, 25 Aug 2025 15:10:19 -0700 Subject: [PATCH 1/4] update vector storage docs - Add Vector DB guide examples (`search`) - Update JS examples to include required "key" field for `upsert` - Update Vector DB guide to reflect both SDKs return `similarity` field - Remove unnecessary distance-to-similarity conversions - Add note about JS backwards compatibility with `distance` parameter --- content/Guides/vector-db.mdx | 320 ++++++++++++++++++++- content/SDKs/javascript/api-reference.mdx | 8 +- content/SDKs/javascript/examples/index.mdx | 3 +- 3 files changed, 322 insertions(+), 9 deletions(-) diff --git a/content/Guides/vector-db.mdx b/content/Guides/vector-db.mdx index ffa5173c..d06d4409 100644 --- a/content/Guides/vector-db.mdx +++ b/content/Guides/vector-db.mdx @@ -44,13 +44,18 @@ 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 `key` field for each document +- **Both SDKs**: Search returns `similarity` field (1.0 = perfect match, 0.0 = no match) +- **Note**: The JavaScript SDK also accepts the `distance` parameter for backwards compatibility, but `similarity` is recommended **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 +67,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 +82,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 +138,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}`); }); ``` @@ -185,6 +193,310 @@ 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 + 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, + 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, + relevanceScore: result.similarity, + // Boost score for exact category matches + finalScore: result.similarity * + (result.metadata.category === category ? 1.2 : 1.0) + })) + .sort((a, b) => b.finalScore - a.finalScore); + + 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["relevanceScore"] = result.similarity + # Boost score for exact category matches + product["finalScore"] = result.similarity * ( + 1.2 if result.metadata.get("category") == category else 1.0 + ) + filtered_results.append(product) + + # Sort by final score + filtered_results.sort(key=lambda x: x["finalScore"], 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) +- **Score boosting** prioritizes exact category matches while maintaining semantic relevance +- **Lower similarity threshold** (0.65) catches more potential matches when using strict filters + +## Common Pitfalls & Solutions + +### No 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: +- JavaScript: Store a unique ID in metadata and check before inserting +- Python: Always use consistent `key` values - upserting with existing key updates + +### Slow Performance? +**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 + +### Wrong 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..d2e52db2 100644 --- a/content/SDKs/javascript/api-reference.mdx +++ b/content/SDKs/javascript/api-reference.mdx @@ -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..3fb15b25 100644 --- a/content/SDKs/javascript/examples/index.mdx +++ b/content/SDKs/javascript/examples/index.mdx @@ -111,6 +111,7 @@ const handler: AgentHandler = async (request, response, context) => { // Prepare documents for vector storage const documents = products.map(product => ({ + key: product.id, document: product.description, metadata: { id: product.id, @@ -145,7 +146,7 @@ const handler: AgentHandler = async (request, response, context) => { // Format results const formattedResults = results.map(result => ({ ...result.metadata, - similarity: 1 - result.distance // Convert distance to similarity score + similarity: result.similarity })); return response.json({ From f919cb401089e08bcf6b713e167bbcac10525d4e Mon Sep 17 00:00:00 2001 From: parteeksingh24 Date: Mon, 25 Aug 2025 15:26:34 -0700 Subject: [PATCH 2/4] Update subheadings in Vector DB guide --- content/Guides/vector-db.mdx | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/content/Guides/vector-db.mdx b/content/Guides/vector-db.mdx index d06d4409..e01a1729 100644 --- a/content/Guides/vector-db.mdx +++ b/content/Guides/vector-db.mdx @@ -451,7 +451,7 @@ async def run(request: AgentRequest, response: AgentResponse, context: AgentCont ## Common Pitfalls & Solutions -### No Search Results? +### Empty Search Results **Problem**: Your search returns empty results even though relevant data exists. **Solutions**: @@ -475,14 +475,15 @@ if (results.length === 0) { } ``` -### Duplicate Documents? +### Duplicate Documents **Problem**: Same content appears multiple times in search results. **Solution**: Vector upsert is idempotent when using the same key: -- JavaScript: Store a unique ID in metadata and check before inserting -- Python: Always use consistent `key` values - upserting with existing key updates +- 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 -### Slow Performance? +### Performance Issues **Problem**: Vector operations take too long. **Solutions**: @@ -490,7 +491,7 @@ if (results.length === 0) { - **Limit search results**: Use `limit: 10` instead of retrieving all matches - **Optimize metadata**: Keep metadata objects small and focused -### Wrong Search Results? +### Irrelevant Search Results **Problem**: Search returns irrelevant or unexpected documents. **Solutions**: From 69d27805d67c8a8ebce9f612c1dfa50b73c6b413 Mon Sep 17 00:00:00 2001 From: parteeksingh24 Date: Mon, 25 Aug 2025 17:33:43 -0700 Subject: [PATCH 3/4] Update based on suggestions --- content/Guides/vector-db.mdx | 38 ++++++++++------------ content/SDKs/javascript/api-reference.mdx | 2 +- content/SDKs/javascript/examples/index.mdx | 13 ++++++-- 3 files changed, 28 insertions(+), 25 deletions(-) diff --git a/content/Guides/vector-db.mdx b/content/Guides/vector-db.mdx index e01a1729..4136320e 100644 --- a/content/Guides/vector-db.mdx +++ b/content/Guides/vector-db.mdx @@ -54,8 +54,6 @@ The `upsert` operation inserts new documents or updates existing ones. You can p **SDK Requirements:** - **Both SDKs**: Require `key` field for each document -- **Both SDKs**: Search returns `similarity` field (1.0 = perfect match, 0.0 = no match) -- **Note**: The JavaScript SDK also accepts the `distance` parameter for backwards compatibility, but `similarity` is recommended **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. @@ -166,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' ); ``` @@ -225,9 +228,9 @@ const handler: AgentHandler = async (request, response, context) => { }); } - // 3. Assemble context from search results + // 3. Assemble context from search results (defensive handling) const contextTexts = searchResults.map(result => - result.metadata.content || result.metadata.text + result.metadata?.content ?? result.metadata?.text ?? '' ); const assembledContext = contextTexts.join('\\n\\n'); @@ -248,7 +251,8 @@ Answer:\`; answer: llmResponse, sources: searchResults.map(r => ({ id: r.id, - title: r.metadata.title, + key: r.key, + title: r.metadata?.title, similarity: r.similarity })) }); @@ -361,12 +365,9 @@ const handler: AgentHandler = async (request, response, context) => { .filter(result => !maxPrice || result.metadata.price <= maxPrice) .map(result => ({ ...result.metadata, - relevanceScore: result.similarity, - // Boost score for exact category matches - finalScore: result.similarity * - (result.metadata.category === category ? 1.2 : 1.0) + similarity: result.similarity })) - .sort((a, b) => b.finalScore - a.finalScore); + .sort((a, b) => b.similarity - a.similarity); return response.json({ query, @@ -419,15 +420,11 @@ async def run(request: AgentRequest, response: AgentResponse, context: AgentCont continue product = dict(result.metadata) - product["relevanceScore"] = result.similarity - # Boost score for exact category matches - product["finalScore"] = result.similarity * ( - 1.2 if result.metadata.get("category") == category else 1.0 - ) + product["similarity"] = result.similarity filtered_results.append(product) - # Sort by final score - filtered_results.sort(key=lambda x: x["finalScore"], reverse=True) + # Sort by similarity score + filtered_results.sort(key=lambda x: x["similarity"], reverse=True) return response.json({ "query": query, @@ -446,7 +443,6 @@ async def run(request: AgentRequest, response: AgentResponse, context: AgentCont **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) -- **Score boosting** prioritizes exact category matches while maintaining semantic relevance - **Lower similarity threshold** (0.65) catches more potential matches when using strict filters ## Common Pitfalls & Solutions diff --git a/content/SDKs/javascript/api-reference.mdx b/content/SDKs/javascript/api-reference.mdx index d2e52db2..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** diff --git a/content/SDKs/javascript/examples/index.mdx b/content/SDKs/javascript/examples/index.mdx index 3fb15b25..be235fc7 100644 --- a/content/SDKs/javascript/examples/index.mdx +++ b/content/SDKs/javascript/examples/index.mdx @@ -109,9 +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: product.id, + key: String(product.id), document: product.description, metadata: { id: product.id, @@ -143,8 +148,10 @@ 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: result.similarity })); From e72fc7ffda7ae3636a460968b1fbd4768f8c0147 Mon Sep 17 00:00:00 2001 From: parteeksingh24 Date: Thu, 28 Aug 2025 15:52:14 -0700 Subject: [PATCH 4/4] Fix grammar in SDK requirement note --- content/Guides/vector-db.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/Guides/vector-db.mdx b/content/Guides/vector-db.mdx index 4136320e..900be0d4 100644 --- a/content/Guides/vector-db.mdx +++ b/content/Guides/vector-db.mdx @@ -53,7 +53,7 @@ For complete API documentation, see: 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 Requirements:** -- **Both SDKs**: Require `key` field for each document +- **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.