Claude Code for Chroma: Embedded Vector Store for AI Apps — Claude Skills 360 Blog
Blog / AI / Claude Code for Chroma: Embedded Vector Store for AI Apps
AI

Claude Code for Chroma: Embedded Vector Store for AI Apps

Published: June 30, 2027
Read time: 5 min read
By: Claude Skills 360

Chroma is an embedded open-source vector store ideal for development and small deployments — new ChromaClient() connects to a running Chroma server (default localhost:8000). new ChromaClient({ path: ":memory:" }) creates in-memory for tests. await client.createCollection({ name, embeddingFunction: new OpenAIEmbeddingFunction({ apiKey }) }) creates a collection. await collection.add({ ids: ["id1"], documents: ["text content"], metadatas: [{ source: "docs" }] }) inserts with auto-embedding. await collection.query({ queryTexts: ["search query"], nResults: 5 }) returns nearest neighbors. where: { source: { $eq: "official-docs" } } filters metadata. where_document: { $contains: "keyword" } filters by document content. await collection.update({ ids: ["id1"], documents: ["new text"] }) updates. await collection.delete({ ids: ["id1"] }) removes entries. collection.peek() inspects first 10 items. Claude Code generates Chroma RAG pipelines, document Q&A, and lightweight similarity search.

CLAUDE.md for Chroma

## Chroma Stack
- Version: chromadb >= 1.9 (JS/TS client for Chroma server)
- Start server: docker run -p 8000:8000 chromadb/chroma (or pip install chromadb && chroma run)
- Init: const client = new ChromaClient({ path: "http://localhost:8000" })
- Collection: const col = await client.getOrCreateCollection({ name: "docs", embeddingFunction: new OpenAIEmbeddingFunction({ openai_api_key: key }) })
- Add: await col.add({ ids, documents, metadatas })
- Query: const results = await col.query({ queryTexts: [query], nResults: 8, where: { category: "tech" } })
- Delete: await col.delete({ where: { docId: { $eq: docId } } })
- Count: await col.count()

Chroma Client and Collections

// lib/chroma/client.ts — ChromaDB client with OpenAI embeddings
import { ChromaClient, OpenAIEmbeddingFunction, type Collection } from "chromadb"

let _client: ChromaClient | null = null
let _collection: Collection | null = null

export const COLLECTION_NAME = process.env.CHROMA_COLLECTION ?? "knowledge-base"

export function getChromaClient(): ChromaClient {
  if (!_client) {
    _client = new ChromaClient({
      path: process.env.CHROMA_URL ?? "http://localhost:8000",
    })
  }
  return _client
}

export function getEmbeddingFunction() {
  return new OpenAIEmbeddingFunction({
    openai_api_key: process.env.OPENAI_API_KEY!,
    openai_model: "text-embedding-3-small",
  })
}

export async function getCollection(): Promise<Collection> {
  if (_collection) return _collection
  const client = getChromaClient()
  _collection = await client.getOrCreateCollection({
    name: COLLECTION_NAME,
    embeddingFunction: getEmbeddingFunction(),
    metadata: { "hnsw:space": "cosine" },
  })
  return _collection
}

/** Reset collection (useful in tests) */
export async function resetCollection(): Promise<Collection> {
  const client = getChromaClient()
  try {
    await client.deleteCollection({ name: COLLECTION_NAME })
  } catch {
    // ignore if not exists
  }
  _collection = null
  return getCollection()
}

Document Store

// lib/chroma/store.ts — add, query, and delete documents
import { getCollection } from "./client"
import { chunkText } from "@/lib/ai/embeddings"

type DocMeta = {
  docId: string
  chunkIndex: number
  title: string
  source: string
  category: string
  userId: string
}

export type SearchHit = {
  id: string
  text: string
  metadata: DocMeta
  distance: number
}

/** Ingest a document — chunks and embeds via Chroma's embedding function */
export async function addDocument(doc: {
  docId: string
  title: string
  content: string
  source: string
  category?: string
  userId?: string
}): Promise<number> {
  const collection = await getCollection()
  const chunks = chunkText(doc.content)

  const ids = chunks.map((_, i) => `${doc.docId}#${i}`)
  const metadatas: DocMeta[] = chunks.map((_, i) => ({
    docId: doc.docId,
    chunkIndex: i,
    title: doc.title,
    source: doc.source,
    category: doc.category ?? "general",
    userId: doc.userId ?? "public",
  }))

  // Chroma accepts batches — add all chunks at once
  await collection.add({
    ids,
    documents: chunks,
    metadatas: metadatas as any,
  })

  return chunks.length
}

/** Semantic search with optional metadata filter */
export async function searchDocuments(
  query: string,
  options: {
    nResults?: number
    category?: string
    userId?: string
  } = {},
): Promise<SearchHit[]> {
  const { nResults = 8, category, userId } = options
  const collection = await getCollection()

  // Build where filter
  const where: Record<string, unknown> = {}
  if (category) where.category = { $eq: category }
  if (userId) {
    // Match user's docs OR public docs — Chroma doesn't support $or natively,
    // so do two queries and merge
    const [userResults, publicResults] = await Promise.all([
      collection.query({
        queryTexts: [query],
        nResults,
        where: { ...where, userId: { $eq: userId } },
      }),
      collection.query({
        queryTexts: [query],
        nResults,
        where: { ...where, userId: { $eq: "public" } },
      }),
    ])

    return mergeAndDedup(query, userResults, publicResults, nResults)
  }

  const result = await collection.query({
    queryTexts: [query],
    nResults,
    ...(Object.keys(where).length ? { where } : {}),
  })

  return formatResults(result)
}

function formatResults(result: Awaited<ReturnType<Awaited<ReturnType<typeof getCollection>>["query"]>>): SearchHit[] {
  const hits: SearchHit[] = []
  const ids = result.ids[0] ?? []
  const docs = result.documents[0] ?? []
  const metas = result.metadatas[0] ?? []
  const dists = result.distances?.[0] ?? []

  ids.forEach((id, i) => {
    hits.push({
      id,
      text: docs[i] ?? "",
      metadata: metas[i] as unknown as DocMeta,
      distance: dists[i] ?? 1,
    })
  })

  return hits
}

function mergeAndDedup(
  _query: string,
  a: Parameters<typeof formatResults>[0],
  b: Parameters<typeof formatResults>[0],
  limit: number,
): SearchHit[] {
  const combined = [...formatResults(a), ...formatResults(b)]
    .sort((x, y) => x.distance - y.distance)

  // Dedup by docId
  const seen = new Set<string>()
  return combined.filter((h) => {
    const key = h.metadata.docId
    if (seen.has(key)) return false
    seen.add(key)
    return true
  }).slice(0, limit)
}

/** Delete all chunks for a document */
export async function deleteDocument(docId: string): Promise<void> {
  const collection = await getCollection()
  const allIds = await collection.get({ where: { docId: { $eq: docId } } })
  if (allIds.ids.length) {
    await collection.delete({ ids: allIds.ids })
  }
}

/** Get collection statistics */
export async function getStats() {
  const collection = await getCollection()
  const count = await collection.count()
  const sample = await collection.peek()
  return { count, sampleIds: sample.ids }
}

RAG Q&A API Route

// app/api/qa/route.ts — RAG question answering with Chroma + OpenAI
import { NextResponse } from "next/server"
import { z } from "zod"
import OpenAI from "openai"
import { searchDocuments } from "@/lib/chroma/store"
import { auth } from "@/lib/auth"

const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY })

const QASchema = z.object({
  question: z.string().min(3).max(600),
  category: z.string().optional(),
})

export async function POST(req: Request) {
  const session = await auth()
  const body = await req.json()
  const { question, category } = QASchema.parse(body)

  // 1. Retrieve relevant chunks
  const hits = await searchDocuments(question, {
    nResults: 5,
    category,
    userId: session?.user?.id,
  })

  if (hits.length === 0) {
    return NextResponse.json({ answer: "I don't have information about that.", sources: [] })
  }

  // 2. Build context from top hits
  const context = hits
    .map((h, i) => `[${i + 1}] ${h.text}`)
    .join("\n\n")

  // 3. Generate answer
  const completion = await openai.chat.completions.create({
    model: "gpt-4o-mini",
    messages: [
      {
        role: "system",
        content: "You are a helpful assistant. Answer based only on the provided context. If the context doesn't contain the answer, say so.",
      },
      {
        role: "user",
        content: `Context:\n${context}\n\nQuestion: ${question}`,
      },
    ],
    temperature: 0.2,
    max_tokens: 512,
  })

  const answer = completion.choices[0].message.content ?? "No answer generated."
  const sources = [...new Set(hits.map((h) => h.metadata.source))]

  return NextResponse.json({ answer, sources, hitCount: hits.length })
}

For the Pinecone alternative when managed, serverless infrastructure with billion-vector scale, sub-millisecond query latency, namespace-based multitenancy, and no infrastructure to operate is required — Pinecone is purpose-built for production AI applications at scale while Chroma is ideal for development, prototyping, and small deployments up to a few million vectors, see the Pinecone guide. For the Weaviate alternative when self-hosted production deployment, GraphQL schema, built-in BM25+vector hybrid search, generative search modules, and multi-tenancy at the class level are needed — Weaviate is the open-source vector database for complex production use cases while Chroma is simpler and better for developer-first RAG prototypes, see the Weaviate guide. The Claude Skills 360 bundle includes Chroma skill sets covering document ingestion, RAG pipelines, and Q&A APIs. Start with the free tier to try embedded vector search generation.

Keep Reading

AI

Claude Code for email.contentmanager: Python Email Content Accessors

Read and write EmailMessage body content with Python's email.contentmanager module and Claude Code — email contentmanager ContentManager for the class that maps content types to get and set handler functions allowing EmailMessage to support get_content and set_content with type-specific behaviour, email contentmanager raw_data_manager for the ContentManager instance that handles raw bytes and str payloads without any conversion, email contentmanager content_manager for the standard ContentManager instance used by email.policy.default that intelligently handles text plain text html multipart and binary content types, email contentmanager get_content_text for the handler that returns the decoded text payload of a text-star message part as a str, email contentmanager get_content_binary for the handler that returns the raw decoded bytes payload of a non-text message part, email contentmanager get_data_manager for the get-handler lookup used by EmailMessage get_content to find the right reader function for the content type, email contentmanager set_content text for the handler that creates and sets a text part correctly choosing charset and transfer encoding, email contentmanager set_content bytes for the handler that creates and sets a binary part with base64 encoding and optional filename Content-Disposition, email contentmanager EmailMessage get_content for the method that reads the message body using the registered content manager handlers, email contentmanager EmailMessage set_content for the method that sets the message body and MIME headers in one call, email contentmanager EmailMessage make_alternative make_mixed make_related for the methods that convert a simple message into a multipart container, email contentmanager EmailMessage add_attachment for the method that attaches a file or bytes to a multipart message, and email contentmanager integration with email.message and email.policy and email.mime and io for building high-level email readers attachment extractors text body accessors HTML readers and policy-aware MIME construction pipelines.

5 min read Feb 12, 2029
AI

Claude Code for email.charset: Python Email Charset Encoding

Control header and body encoding for international email with Python's email.charset module and Claude Code — email charset Charset for the class that wraps a character set name with the encoding rules for header encoding and body encoding describing how to encode text for that charset in email messages, email charset Charset header_encoding for the attribute specifying whether headers using this charset should use QP quoted-printable encoding BASE64 encoding or no encoding, email charset Charset body_encoding for the attribute specifying the Content-Transfer-Encoding to use for message bodies in this charset such as QP or BASE64, email charset Charset output_codec for the attribute giving the Python codec name used to encode the string to bytes for the wire format, email charset Charset input_codec for the attribute giving the Python codec name used to decode incoming bytes to str, email charset Charset get_output_charset for returning the output charset name, email charset Charset header_encode for encoding a header string using the charset's header_encoding method, email charset Charset body_encode for encoding body content using the charset's body_encoding, email charset Charset convert for converting a string from the input_codec to the output_codec, email charset add_charset for registering a new charset with custom encoding rules in the global charset registry, email charset add_alias for adding an alias name that maps to an existing registered charset, email charset add_codec for registering a codec name mapping for use by the charset machinery, and email charset integration with email.message and email.mime and email.policy and email.encoders for building international email senders non-ASCII header encoders Content-Transfer-Encoding selectors charset-aware message constructors and MIME encoding pipelines.

5 min read Feb 11, 2029
AI

Claude Code for email.utils: Python Email Address and Header Utilities

Parse and format RFC 2822 email addresses and dates with Python's email.utils module and Claude Code — email utils parseaddr for splitting a display-name plus angle-bracket address string into a realname and email address tuple, email utils formataddr for combining a realname and address string into a properly quoted RFC 2822 address with angle brackets, email utils getaddresses for parsing a list of raw address header strings each potentially containing multiple comma-separated addresses into a list of realname address tuples, email utils parsedate for parsing an RFC 2822 date string into a nine-tuple compatible with time.mktime, email utils parsedate_tz for parsing an RFC 2822 date string into a ten-tuple that includes the UTC offset timezone in seconds, email utils parsedate_to_datetime for parsing an RFC 2822 date string into an aware datetime object with timezone, email utils formatdate for formatting a POSIX timestamp or the current time as an RFC 2822 date string with optional usegmt and localtime flags, email utils format_datetime for formatting a datetime object as an RFC 2822 date string, email utils make_msgid for generating a globally unique Message-ID string with optional idstring and domain components, email utils decode_rfc2231 for decoding an RFC 2231 encoded parameter value into a tuple of charset language and value, email utils encode_rfc2231 for encoding a string as an RFC 2231 encoded parameter value, email utils collapse_rfc2231_value for collapsing a decoded RFC 2231 tuple to a Unicode string, and email utils integration with email.message and email.headerregistry and datetime and time for building address parsers date formatters message-id generators header extractors and RFC-compliant email construction utilities.

5 min read Feb 10, 2029

Put these ideas into practice

Claude Skills 360 gives you production-ready skills for everything in this article — and 2,350+ more. Start free or go all-in.

Back to Blog

Get 360 skills free