Skip to main content

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:
  1. Visit platform.getalchemystai.com/context
  2. You should see your document listed
  3. 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

ValueMeaningWhen to Use
0.5Somewhat relevantExploratory searches, broad topics
0.7RelevantStart here - good balance
0.9Very relevantPrecise 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\nQuestion: ${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\nQuestion: ${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.

Next: Add Real Documents

Now that you understand the basics, let’s add real documents:
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");

Advanced: Organize with Metadata

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?

BenefitDescription
Faster searchesSmaller search space = lower latency
More relevantOnly search customer-support docs, not engineering docs
Better organizationHierarchical structure like folders
Access controlFilter 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:
  • Getting documents that don’t match the query
  • Too many results to process efficiently
Common causes:
  1. similarity_threshold is too low
  2. Documents are too broad or generic
  3. 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"
    }
  }]
});
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
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:

Check Platform UI

  1. Visit platform.getalchemystai.com/context
  2. You should see your stored documents
  3. Click to view chunks and embeddings
  4. 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?