Rag
Claude Plugin - Personal knowledge base for Claude Code — patterns and integrations across projects
npx -y skills add phucbm/skills --skill ragAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
- 2 stars2 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Build a RAG (Retrieval-Augmented Generation) pipeline — chunk data, embed, store in vector DB, query at inference, inject into system prompt. Use when adding a custom knowledge base to an AI chatbot.
SKILL.md
4.4 KB, as published. Nobody here has run it
RAG Pipeline
RAG (Retrieval-Augmented Generation) gives an AI model access to a custom knowledge base by retrieving relevant chunks at inference and injecting them into the prompt.
Concepts
Embedding model — a small, specialized model (separate from the chat LLM) that converts text into a list of numbers (a vector) capturing semantic meaning. Similar text produces similar vectors. Called via OpenAI, Gemini, etc. embedding APIs — not the chat model.
Vector DB — a search index, not an answering engine. Stores vectors + original text as metadata. At query time, returns the top-K most semantically similar text chunks. The chat LLM never reads the vector DB directly.
Chat LLM — generates the final answer. It only sees plain text: the system prompt, injected chunks, and the user question. RAG just gives it better context.
Ingest (run once, re-run when data changes)
| Step | Action | API token? |
|---|---|---|
| 1 | Read files (CSV, MD) and split into text chunks with metadata | No |
| 2 | Call embedding model — convert each chunk to a vector | Yes (embedding API) |
| 3 | Upsert vectors + original text into vector DB | No |
Query (every user message)
| Step | Action | API token? |
|---|---|---|
| 1 | Receive user question | No |
| 2 | Call embedding model — convert question to a vector | Yes (embedding API) |
| 3 | Search vector DB — return top-K matching text chunks (not answers) | No |
| 4 | Inject chunks into system prompt as plain text | No |
| 5 | Call chat LLM — read system prompt + question, generate answer | Yes (chat API) |
| 6 | Stream response to UI | No |
Embedding and chat use separate API calls — often different providers. Re-ingesting is expensive at scale (step 2 x every chunk). Query costs 2 API calls per message minimum.
Key decision: inject RAG context at prompt level, not as a tool call. Keeps retrieval invisible to the model's reasoning, avoids tool overhead.
Chunk type
type Chunk = {
id: string; // stable, unique — used as vector DB record ID
text: string; // content to embed and return at query time
metadata: {
source: string;
[key: string]: string | string[] | number;
};
};
Ingest script pattern
for (const file of dataFiles) {
const chunks = file.endsWith(".csv")
? chunkCSV(filePath, source)
: chunkMarkdown(filePath, source);
const embeddings = await generateEmbeddings(chunks.map(c => c.text));
const vectors = chunks.map((chunk, i) => ({
id: chunk.id,
values: embeddings[i],
metadata: chunk.metadata,
}));
await upsertVectors(vectors, NAMESPACE); // batch in groups of 100
}
Switchable embedding provider
// EMBEDDING_PROVIDER=gemini (default) | openai
async function generateEmbeddings(texts: string[]): Promise<number[][]> {
const provider = process.env.EMBEDDING_PROVIDER || "gemini";
if (provider === "openai") return embedWithOpenAI(texts);
return embedWithGemini(texts);
}
Inject into system prompt
const ragContext = await queryVectors(embed(userMessage), topK);
const systemPrompt = ragContext.length > 0
? `${basePrompt}\n\n## Relevant Information\n${ragContext.map(r => r.metadata.text).join("\n\n")}`
: basePrompt;
RAG_TOP_K tuning
RAG_TOP_K=5— default, pure semantic searchRAG_TOP_K_FILTERED=10-30— use higher K when metadata filters active (filtering reduces recall before ranking)
Auto-ingest via GitHub Actions
on:
push:
branches: [main]
paths: ["data/**"]
Triggers ingestion automatically when data files change — no manual pnpm ingest needed on deploy.
References
- food-tour-sg
scripts/ingest.ts: https://github.com/phucbm/food-tour-sg/blob/main/scripts/ingest.ts - food-tour-sg
lib/rag/embeddings.ts: https://github.com/phucbm/food-tour-sg/blob/main/lib/rag/embeddings.ts - food-tour-sg
lib/ai/prompts.ts: https://github.com/phucbm/food-tour-sg/blob/main/lib/ai/prompts.ts - Vector DB implementation: see
pineconeskill