{"_id":"@canarycoders/ai-rag","name":"@canarycoders/ai-rag","dist-tags":{"latest":"0.2.0"},"versions":{"0.2.0":{"name":"@canarycoders/ai-rag","version":"0.2.0","description":"Bun-first RAG toolkit for CanaryCoders AI: parse, chunk, embed (via the gateway) and store vectors in your own pgvector/sqlite store. Nothing leaves your infrastructure except transient embedding calls.","license":"MIT","author":{"name":"CanaryCoders"},"type":"module","publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"main":"./dist/index.cjs","module":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"},"./store/pgvector":{"types":"./dist/store/pgvector.d.ts","import":"./dist/store/pgvector.js","require":"./dist/store/pgvector.cjs"}},"scripts":{"typecheck":"tsc --noEmit","test":"bun test test/unit","build":"tsup","prepare":"tsup","prepublishOnly":"bun run build && bun run typecheck && bun run test"},"keywords":["canaryllm","rag","embeddings","pgvector","retrieval","bun"],"engines":{"node":">=18"},"dependencies":{"@canarycoders/ai":"^0.3.0"},"devDependencies":{"@types/bun":"latest","tsup":"^8.0.0","typescript":"^5.5.0"},"_id":"@canarycoders/ai-rag@0.2.0","_nodeVersion":"25.6.1","_npmVersion":"9.6.6","dist":{"integrity":"sha512-TCo+pQtonJ2SbC0JoFaK3WgNVI6hJBhjwD5M1C5m2j5Hi4X/VAIn65xZzQE4co8Vf+Ef8fvd7cr8XcXfaOOcwQ==","shasum":"e53b936b0ce99683abcadef4996f86b7a9e72f31","tarball":"https://registry.npmjs.org/@canarycoders/ai-rag/-/ai-rag-0.2.0.tgz","fileCount":17,"unpackedSize":96586,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQDz1I6IKPEF/uHFx0aw8q8Um5Jj+0Z2fNZCW79/UL27aQIgdkcdpvAxbzMuwy9PIC7KR8ICt9EeXMS6emfWrIp0XJg="}]},"_npmUser":{"name":"canarycoderssl","email":"kai@canarycoders.es"},"directories":{},"maintainers":[{"name":"canarycoderssl","email":"kai@canarycoders.es"},{"name":"kyandesutter","email":"kyan@canarycoders.es"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/ai-rag_0.2.0_1781741702098_0.24203716164050726"},"_hasShrinkwrap":false}},"time":{"created":"2026-06-18T00:15:01.924Z","0.2.0":"2026-06-18T00:15:02.268Z","modified":"2026-06-18T00:15:02.481Z"},"maintainers":[{"name":"canarycoderssl","email":"kai@canarycoders.es"},{"name":"kyandesutter","email":"kyan@canarycoders.es"}],"description":"Bun-first RAG toolkit for CanaryCoders AI: parse, chunk, embed (via the gateway) and store vectors in your own pgvector/sqlite store. Nothing leaves your infrastructure except transient embedding calls.","keywords":["canaryllm","rag","embeddings","pgvector","retrieval","bun"],"author":{"name":"CanaryCoders"},"license":"MIT","readme":"# @canarycoders/ai-rag\n\nBun-first RAG toolkit for [CanaryCoders AI](https://ai.canarycoders.es). Parse, chunk, embed and retrieve over your own documents — **the documents, chunks, embeddings and vector index all stay on your infrastructure.** CanaryLLM only does the transient embedding and chat calls; it stores nothing.\n\nIt builds on the official SDK ([`@canarycoders/ai`](https://github.com/CanaryCoders/canaryllm-sdk)) and adds the RAG layer: a recursive token chunker, text extraction, a pluggable vector store (pgvector adapter included), and ingest/retrieve orchestration.\n\n## Why this shape\n\nEmbeddings derived from your documents are themselves personal data (text can be partially reconstructed from a vector). Keeping the vectors in *your* store — not ours — keeps you in control of access, retention and erasure, and means no document content is ever persisted by the gateway. The only thing that crosses the wire is chunk text, embedded transiently on local (LM Studio) inference with no third-country transfer.\n\n## Install\n\n```bash\nbun add @canarycoders/ai-rag @canarycoders/ai\n```\n\nYou also need a Postgres with the [`pgvector`](https://github.com/pgvector/pgvector) extension (or implement the `VectorStore` interface for another store), and an LM Studio embedding model loaded behind your CanaryLLM gateway (e.g. `nomic-embed-text-v1.5`).\n\n## Quick start (pgvector)\n\n```ts\nimport { Pool } from \"pg\";\nimport { CanaryLLM } from \"@canarycoders/ai\";\nimport { canaryEmbedder, ingestDocuments, retrieve, buildRagMessages } from \"@canarycoders/ai-rag\";\nimport { PgVectorStore } from \"@canarycoders/ai-rag/store/pgvector\";\n\nconst client = new CanaryLLM({ apiKey: process.env.CANARYLLM_API_KEY });\nconst embedder = canaryEmbedder(client, { model: \"nomic-embed-text-v1.5\" });\n\nconst pool = new Pool({ connectionString: process.env.DATABASE_URL });\nconst store = new PgVectorStore(pool, { dimensions: 768 }); // match your model\nawait store.migrate(); // creates extension, table, HNSW cosine index\n\n// 1. Ingest — chunk → embed → upsert (your store, your data)\nawait ingestDocuments(\n  [\n    { id: \"handbook.md\", text: handbookText, metadata: { source: \"handbook.md\" } },\n    { id: \"policy.md\", text: policyText, metadata: { source: \"policy.md\" } },\n  ],\n  { embedder, store, chunk: { chunkSize: 512, chunkOverlap: 64 } },\n);\n\n// 2. Retrieve — embed the question, search your store\nconst hits = await retrieve(\"How many vacation days do I get?\", { embedder, store, topK: 5 });\n\n// 3. Answer — grounded completion via the gateway\nconst messages = buildRagMessages(\"How many vacation days do I get?\", hits);\nconst answer = await client.chat.complete({ provider: \"lmstudio\", model: \"qwen3-32b\", messages });\nconsole.log(answer.content);\n```\n\n## Parsing\n\nThe toolkit ingests plain text (`RagDocument.text`). For HTML and Markdown, use the built-in extractors:\n\n```ts\nimport { htmlToText, markdownToText, extractText } from \"@canarycoders/ai-rag\";\n\nconst text = htmlToText(rawHtml);\nconst md = markdownToText(rawMarkdown);\nconst auto = extractText(content, \"html\"); // dispatch by extension/mime hint\n```\n\nFor **PDF/DOCX**, extract on your side (so the raw file never leaves your box) and pass the resulting string in:\n\n```ts\nimport pdf from \"pdf-parse\";        // your dependency\nimport mammoth from \"mammoth\";       // your dependency\n\nconst { text: pdfText } = await pdf(await Bun.file(\"contract.pdf\").arrayBuffer());\nconst { value: docxText } = await mammoth.extractRawText({ buffer: await Bun.file(\"brief.docx\").arrayBuffer() });\n\nawait ingestDocuments(\n  [{ id: \"contract.pdf\", text: pdfText, metadata: { source: \"contract.pdf\" } }],\n  { embedder, store },\n);\n```\n\n## Chunking\n\n`chunkDocument` / `splitTextIntoChunks` use a recursive splitter (paragraph → line → sentence → word → char) that packs pieces up to `chunkSize` tokens with `chunkOverlap` tokens of carry-over. The default token count is a ~4-chars-per-token estimate; pass a real tokenizer for exact sizing:\n\n```ts\nimport { splitTextIntoChunks } from \"@canarycoders/ai-rag\";\n\nconst chunks = splitTextIntoChunks(text, {\n  chunkSize: 512,\n  chunkOverlap: 64,\n  countTokens: (t) => myTokenizer.encode(t).length,\n});\n```\n\n## Custom stores\n\nImplement `VectorStore` to back the toolkit with sqlite-vec, Qdrant, Weaviate, etc.:\n\n```ts\nimport type { VectorStore } from \"@canarycoders/ai-rag\";\n```\n\n`PgVectorStore` takes any node-postgres-shaped client (`{ query(sql, params) }`), so the `pg` driver and your credentials stay your dependency — they never touch this package.\n\n## Local development\n\nThis package depends on `@canarycoders/ai`. Until both are published to npm, link the SDK for local work:\n\n```bash\ncd ../canaryllm-sdk && bun link\ncd ../canaryllm-rag && bun link @canarycoders/ai && bun install\n```\n\n```bash\nbun run typecheck\nbun test\nbun run build\n```\n\n## Roadmap\n\nShipped (v0.1): embeddings client, recursive chunker, HTML/Markdown extraction, pgvector store, ingest/retrieve, grounded-answer message builder.\n\nPlanned: sqlite-vec adapter, IMAP and Microsoft Graph email connectors, a watch/poll ingestion sidecar, reranking, hybrid (BM25 + vector) search, and retrieval-quality evaluation tooling.\n\n## License\n\nMIT\n","readmeFilename":"README.md","_rev":"1-8350848bd3ef3ed89b18f610e444997a"}