What You’re Building
An AI that answers questions using YOUR documents instead of guessing.
Before Alchemyst:
const answer = await llm . generate ( "What's our refund policy?" );
// "I don't have information about your specific refund policy..." ❌
After Alchemyst:
const answer = await llm . generate ( "What's our refund policy?" );
// "We offer a 30-day money back guarantee. Contact support@example.com..." ✅
Prerequisites
Alchemyst account (sign up )
Your ALCHEMYST_AI_API_KEY
Node.js 18+ or Python 3.9+
Time to complete: 10 minutes
Step 1: Install
npm install @alchemystai/sdk
Step 2: Initialize
import AlchemystAI from '@alchemystai/sdk' ;
const client = new AlchemystAI ({
apiKey: process . env . ALCHEMYST_AI_API_KEY ,
});
Step 3: Store a Document
const result = await client . v1 . context . add ({
documents: [{
content: "Our refund policy: We offer a 30-day money back guarantee. Contact support@example.com to request a refund."
}],
context_type: 'resource' ,
source: 'documentation' ,
scope: 'internal'
});
console . log ( `✅ Stored ${ result . documents ?. length || 0 } documents` );
// Output: ✅ Stored 1 documents
What just happened:
Your document was chunked into searchable pieces (~500 word chunks)
Embeddings were generated automatically (vector representations)
Everything is indexed and ready to search
Verify it worked:
Visit platform.getalchemystai.com/context
You should see your document listed
Click to view chunks and metadata
Step 4: Search for Context
const userQuestion = "What's your refund policy?" ;
const { contexts } = await client . v1 . context . search ({
query: userQuestion ,
similarity_threshold: 0.7 ,
scope: 'internal'
});
console . log ( `Found ${ contexts ?. length || 0 } relevant chunks` );
// Output: Found 1 relevant chunks
console . log ( contexts [ 0 ]. content );
// Output: "Our refund policy: We offer a 30-day money back guarantee..."
Understanding similarity_threshold
Value Meaning When to Use 0.5Somewhat relevant Exploratory searches, broad topics 0.7Relevant Start here - good balance0.9Very relevant Precise matches, technical queries
Tip: Start at 0.7, lower to 0.5 if you get no results, raise to 0.9 if results are too broad.
Step 5: Feed Context to Your LLM
import OpenAI from 'openai' ;
const openai = new OpenAI ();
const prompt = contexts ?. length
? `Context: \n ${ contexts . map ( c => c . content ). join ( ' \n\n ' ) } \n\n Question: ${ userQuestion } `
: userQuestion ;
const response = await openai . chat . completions . create ({
model: "gpt-4" ,
messages: [{ role: "user" , content: prompt }]
});
console . log ( response . choices [ 0 ]. message . content );
// Output: "We offer a 30-day money back guarantee. To request a refund, contact support@example.com."
Result: Your AI now answers from YOUR data, not generic training data.
Complete Working Example
import AlchemystAI from '@alchemystai/sdk' ;
import OpenAI from 'openai' ;
const alchemyst = new AlchemystAI ({
apiKey: process . env . ALCHEMYST_AI_API_KEY ,
});
const openai = new OpenAI ();
async function main () {
// 1. Store document
await alchemyst . v1 . context . add ({
documents: [{
content: "Our refund policy: We offer a 30-day money back guarantee. Contact support@example.com to request a refund."
}],
context_type: 'resource' ,
source: 'docs' ,
scope: 'internal'
});
console . log ( "✅ Document stored" );
// 2. Search for context
const userQuestion = "What's your refund policy?" ;
const { contexts } = await alchemyst . v1 . context . search ({
query: userQuestion ,
similarity_threshold: 0.7 ,
scope: 'internal'
});
console . log ( `Found ${ contexts ?. length } relevant chunks` );
// 3. Generate answer with context
const prompt = contexts ?. length
? `Context: \n ${ contexts . map ( c => c . content ). join ( ' \n\n ' ) } \n\n Question: ${ userQuestion } `
: userQuestion ;
const response = await openai . chat . completions . create ({
model: "gpt-4" ,
messages: [{ role: "user" , content: prompt }]
});
console . log ( "AI Response:" , response . choices [ 0 ]. message . content );
}
main ();
Expected Output: ✅ Document stored
Found 1 relevant chunks
AI Response: We offer a 30-day money back guarantee. To request a refund, contact support@example.com.
import os
from alchemyst_ai import AlchemystAI
import openai
alchemyst = AlchemystAI( api_key = os.environ.get( "ALCHEMYST_AI_API_KEY" ))
def main ():
# 1. Store document
alchemyst.v1.context.add(
documents = [{
"content" : "Our refund policy: We offer a 30-day money back guarantee. Contact support@example.com to request a refund."
}],
context_type = "resource" ,
source = "docs" ,
scope = "internal"
)
print ( "✅ Document stored" )
# 2. Search for context
user_question = "What's your refund policy?"
result = alchemyst.v1.context.search(
query = user_question,
similarity_threshold = 0.7 ,
scope = "internal"
)
contexts = result.contexts or []
print ( f "Found { len (contexts) } relevant chunks" )
# 3. Generate answer with context
if contexts:
context_text = " \n\n " .join([ctx.content for ctx in contexts])
prompt = f "Context: \n { context_text } \n\n Question: { user_question } "
else :
prompt = user_question
response = openai.chat.completions.create(
model = "gpt-4" ,
messages = [{ "role" : "user" , "content" : prompt}]
)
print ( "AI Response:" , response.choices[ 0 ].message.content)
if __name__ == "__main__" :
main()
Expected Output: ✅ Document stored
Found 1 relevant chunks
AI Response: We offer a 30-day money back guarantee. To request a refund, contact support@example.com.
Next: Add Real Documents
Now that you understand the basics, let’s add real documents:
From File
From API
Multiple Files
import fs from 'fs' ;
const content = fs . readFileSync ( './docs/policy.txt' , 'utf-8' );
await client . v1 . context . add ({
documents: [{
content: content ,
metadata: {
file_name: "policy.txt" ,
file_type: "text"
}
}],
context_type: 'resource' ,
source: 'documentation' ,
scope: 'internal'
});
console . log ( "✅ File uploaded and indexed" );
const response = await fetch ( 'https://api.example.com/docs' );
const apiDocs = await response . json ();
const docs = apiDocs . map ( doc => ({
content: doc . body ,
metadata: {
file_name: doc . title ,
doc_id: doc . id
}
}));
await client . v1 . context . add ({
documents: docs ,
context_type: 'resource' ,
source: 'api' ,
scope: 'internal'
});
console . log ( `✅ Indexed ${ docs . length } documents from API` );
const docs = [
{
content: "Refund policy: We offer a 30-day money back guarantee..." ,
metadata: { file_name: "refunds.md" }
},
{
content: "Shipping policy: We ship worldwide within 5-7 business days..." ,
metadata: { file_name: "shipping.md" }
},
{
content: "Privacy policy: We collect and protect your data..." ,
metadata: { file_name: "privacy.md" }
}
];
await client . v1 . context . add ({
documents: docs ,
context_type: 'resource' ,
source: 'documentation' ,
scope: 'internal'
});
console . log ( `✅ Indexed ${ docs . length } policy documents` );
Use group_name to filter searches by category:
// Store with groups
await client . v1 . context . add ({
documents: [{
content: "Refund policy: We offer a 30-day money back guarantee..." ,
metadata: {
file_name: "refund-policy.md" ,
group_name: [ "customer-support" , "policies" ] // ← For storage
}
}],
context_type: 'resource' ,
source: 'documentation' ,
scope: 'internal'
});
// Search within specific groups
const { contexts } = await client . v1 . context . search ({
query: "refund policy" ,
scope: 'internal' ,
metadata: {
groupName: [ 'customer-support' ] // ← For search (note: camelCase)
}
});
console . log ( `Found ${ contexts ?. length } results in customer-support` );
Naming difference: Storage uses group_name (snake_case in metadata) but TypeScript search uses groupName (camelCase). Python uses group_name consistently. Both refer to the same field - this is due to API design conventions.
Why use groups?
Benefit Description Faster searches Smaller search space = lower latency More relevant Only search customer-support docs, not engineering docs Better organization Hierarchical structure like folders Access control Filter by team, project, or user permissions
Example hierarchy:
group_name : [ "company" , "customer-support" , "policies" ]
group_name : [ "company" , "engineering" , "api-docs" ]
group_name : [ "company" , "hr" , "employee-handbook" ]
Learn more: Context Arithmetic
Troubleshooting
Error symptoms:
contexts?.length returns 0
Empty array in search results
Common causes:
similarity_threshold is too high
Document wasn’t stored successfully
scope mismatch between add and search
Fixes: Step 1: Lower the threshold: similarity_threshold : 0.5 // Instead of 0.9
Step 2: Verify documents were stored: const stored = await client . v1 . context . view ();
console . log ( "Total documents:" , stored . length );
console . log ( "Documents:" , stored );
Expected output: Total documents: 1
Documents: [{ id: "doc_123", content: "Our refund policy...", ... }]
Step 3: Check scope matches: // Both must use the same scope
await client . v1 . context . add ({ scope: 'internal' , ... });
await client . v1 . context . search ({ scope: 'internal' , ... }); // ✅ Match
Too many irrelevant results
Error symptoms:
Getting documents that don’t match the query
Too many results to process efficiently
Common causes:
similarity_threshold is too low
Documents are too broad or generic
Not using group filtering
Fixes: Step 1: Raise the threshold: similarity_threshold : 0.8 // More strict (was 0.5)
Step 2: Use group filtering: metadata : {
groupName : [ 'customer-support' ] // Narrow to specific category
}
Step 3: Add more specific metadata: await client . v1 . context . add ({
documents: [{
content: "..." ,
metadata: {
group_name: [ "support" , "refunds" ], // More specific
category: "policies" ,
department: "customer-service"
}
}]
});
Document already exists error
Error message: {
"error" : "Document with file_name 'policy.md' already exists" ,
"code" : "CONFLICT" ,
"status" : 409
}
Cause: Trying to add a document with duplicate file_name in metadata.Fixes: Step 1: Delete old version first: // Delete by file name
await client . v1 . context . delete ({
metadata: { fileName: "policy.md" }
});
// Then add new version
await client . v1 . context . add ({
documents: [{
content: "Updated content..." ,
metadata: { file_name: "policy.md" }
}]
});
Step 2: Use versioned names: file_name : "policy-v2.md" // or "policy-2024-02-01.md"
Step 3: Use unique identifiers: file_name : `policy- ${ Date . now () } .md` // Timestamp
// or
file_name : `policy- ${ uuid () } .md` // UUID
API authentication errors
Error message: {
"error" : "Invalid API key" ,
"code" : "UNAUTHORIZED" ,
"status" : 401
}
Fixes: Step 1: Verify API key is set: console . log ( "API Key exists:" , !! process . env . ALCHEMYST_AI_API_KEY );
// Should output: API Key exists: true
Step 2: Check key format: // Should start with "alch_" or similar prefix
console . log ( "Key prefix:" , process . env . ALCHEMYST_AI_API_KEY ?. substring ( 0 , 5 ));
Step 3: Get a new key:
Error message: {
"error" : "Rate limit exceeded" ,
"code" : "RATE_LIMIT" ,
"status" : 429 ,
"retry_after" : 60
}
Cause: Too many requests in a short time period.Fixes: Step 1: Add retry logic: async function searchWithRetry ( query : string , retries = 3 ) {
for ( let i = 0 ; i < retries ; i ++ ) {
try {
return await client . v1 . context . search ({ query , scope: 'internal' });
} catch ( error ) {
if ( error . status === 429 && i < retries - 1 ) {
const waitTime = error . retry_after || 60 ;
console . log ( `Rate limited. Waiting ${ waitTime } s...` );
await new Promise ( resolve => setTimeout ( resolve , waitTime * 1000 ));
} else {
throw error ;
}
}
}
}
Step 2: Check your rate limits:
Free tier: 100 operations/day
Pro tier: 10,000 operations/day
Step 3: Upgrade your plan: View pricing
Best Practices
Document Organization
// ✅ Good - organized with meaningful metadata
await client . v1 . context . add ({
documents: [{
content: "..." ,
metadata: {
file_name: "refund-policy-2024-02.md" ,
group_name: [ "customer-support" , "policies" ],
department: "support" ,
last_updated: "2024-02-01"
}
}]
});
// ❌ Bad - minimal metadata, hard to search
await client . v1 . context . add ({
documents: [{
content: "..."
}]
});
Chunk Size Considerations
Small documents (under 1000 words): Add as-is
Medium documents (1000-5000 words): Let Alchemyst auto-chunk
Large documents (over 5000 words): Consider splitting by section
// For large documents, split manually for better control
const sections = [
{ title: "Introduction" , content: "..." },
{ title: "Features" , content: "..." },
{ title: "Pricing" , content: "..." }
];
const docs = sections . map ( section => ({
content: section . content ,
metadata: {
file_name: "product-docs.md" ,
section: section . title
}
}));
await client . v1 . context . add ({ documents: docs });
Search Optimization
// ✅ Good - specific query with filtering
const { contexts } = await client . v1 . context . search ({
query: "How to request a refund for damaged items" ,
scope: 'internal' ,
similarity_threshold: 0.7 ,
metadata: {
groupName: [ 'customer-support' , 'refunds' ]
}
});
// ❌ Bad - too broad, no filtering
const { contexts } = await client . v1 . context . search ({
query: "help" ,
scope: 'internal'
});
Error Handling
async function safeSearch ( query : string ) {
try {
const { contexts } = await client . v1 . context . search ({
query ,
scope: 'internal' ,
similarity_threshold: 0.7
});
if ( ! contexts || contexts . length === 0 ) {
console . log ( "No results found. Try a broader query." );
return null ;
}
return contexts ;
} catch ( error ) {
console . error ( "Search failed:" , error . message );
// Fallback or retry logic here
return null ;
}
}
Verify Your Setup
After implementing context search, verify everything is working:
Visit platform.getalchemystai.com/context
You should see your stored documents
Click to view chunks and embeddings
Check document count matches what you uploaded
Test with Code
// Test the complete flow
async function testSetup () {
// 1. Store a test document
console . log ( "Step 1: Storing test document..." );
await client . v1 . context . add ({
documents: [{
content: "Test document: The answer is 42" ,
metadata: { file_name: "test.txt" }
}],
scope: 'internal'
});
// 2. Search for it
console . log ( "Step 2: Searching for test document..." );
const { contexts } = await client . v1 . context . search ({
query: "What is the answer?" ,
scope: 'internal' ,
similarity_threshold: 0.5
});
// 3. Verify results
const found = contexts ?. some ( c => c . content . includes ( "42" ));
console . log ( "✅ Test passed:" , found );
// 4. Cleanup
console . log ( "Step 3: Cleaning up..." );
await client . v1 . context . delete ({
metadata: { fileName: "test.txt" }
});
}
testSetup ();
Expected Output:
Step 1: Storing test document...
Step 2: Searching for test document...
✅ Test passed: true
Step 3: Cleaning up...
What’s Next?
Learn Advanced Patterns
Need Help?