Installs into .claude/skills of the current project.
Are you the author of Llm Integration?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/dev-toolbelt-llm-integration)
---
name: llm-integration
description: LLM/RAG in product — retrieval, chunking, prompt versioning, eval, cost, failure modes.
---
# LLM Integration
How to put a language model inside a product feature: retrieval, prompting, evaluation, cost, and the failure modes that only appear in production.
## When to Load
Load when the project ships a feature whose output is produced by a language model — semantic search, assistant/chat, summarization, extraction, classification, agentic tool-calling, or "ask your data".
Do **not** load for using an AI coding assistant to write the project's code. That is tooling, not product architecture.
> Model names, providers and API shapes below are **illustrative only**. Detect the provider the project already uses (SDK in the dependency manifest, `*_API_KEY` in env templates, gateway config) and follow its own documentation. Never introduce a provider the project has not chosen.
---
## Step 0 — Choose the Cheapest Mechanism That Works
Escalate only when the tier below fails an evaluation, never by default.
| Tier | Mechanism | Use when | Cost of change |
|---|---|---|---|
| 1 | **No model** (deterministic code, full-text search, rules) | Output is derivable from data | None |
| 2 | **Prompt only** | Task fits in instructions; knowledge is in the model or the request | Edit a string |
| 3 | **Prompt + few-shot examples** | Output shape or tone is hard to describe but easy to demonstrate | Edit a string |
| 4 | **RAG** (retrieval-augmented generation) | Answers depend on private, large, or frequently changing corpora | Ingestion pipeline + store |
| 5 | **Tool / function calling** | The model must read live state or cause effects | Tool contracts + authz per tool |
| 6 | **Fine-tuning** | Style/format is stable and demonstrably unteachable by prompt | Training + retraining treadmill |
Rules:
- RAG fixes **missing knowledge**. Fine-tuning fixes **behavior and format**. Using either for the other's problem is the most common architectural mistake in this space.
- Fine-tuning freezes knowledge into weights — a corpus that changes weekly must be retrieved, not trained.
- Tool calling turns the model into a caller of your API. Every tool must enforce the **caller's** authorization, never the model's intent (see `skills/security/idor/SKILL.md`).
---
## Ingestion and Chunking
The retrieval quality ceiling is set at ingestion time. A chunking mistake cannot be recovered by a better model.
| Decision | Guidance |
|---|---|
| Chunk boundary | Split on **document structure** (heading, section, article, row, function) before falling back to fixed size |
| Chunk size | Small enough that one chunk answers one question; large enough to stand alone without its neighbours |
| Overlap | Modest overlap prevents cutting a fact in half; heavy overlap inflates index size and returns near-duplicates |
| Context header | Prepend document title / section path to each chunk so an isolated chunk remains interpretable |
| Tables and code | Never split a table from its header row or a function from its signature — chunk them whole or summarize |
| Metadata | Store `source_id`, `version`, `updated_at`, tenant/ACL keys, and section path alongside every vector |
Pipeline requirements:
- **Idempotent re-ingestion** — re-processing a document must replace, not duplicate, its chunks. Key on `(source_id, chunk_index)` or delete-then-insert per document.
- **Deletion propagation** — when a source record is deleted or unshared, its vectors must be deleted too. Orphan vectors leak data forever.
- **Re-embed on model change** — an index mixing two embedding models is silently broken. Store the embedding model + version per row and re-index on change.
- Treat ingestion as a background job with retries and dead-letter handling (`skills/architecture/async-jobs/SKILL.md`).
---
## Embeddings
| Property | What to check before choosing |
|---|---|
| Dimensionality | Directly drives index size, memory and query cost — larger is not automatically better |
| Domain fit | General-purpose embeddings underperform on code, legal, medical and non-English corpora |
| Symmetry | Some models expect distinct query vs document prefixes; mismatching them quietly degrades recall |
| Max input | Inputs longer than the limit are truncated, often **silently** |
| Normalization | Cosine similarity assumes normalized vectors; verify the model's convention |
Rules:
- Embed the **same normalized text** at query time and at index time (same casing, same stripping, same prefix).
- Cache embeddings keyed by `hash(text) + model + version`. Re-embedding unchanged content is pure waste.
- Batch embedding calls at ingestion; concurrency at query time is latency, not throughput.
See `references/provider-embeddings.md` for a model/feature comparison across OpenAI, Anthropic (Voyage AI), Google and Cohere, and for provider-specific RAG features (e.g. Contextual Retrieval, managed retrieval, rerank endpoints).
---
## Vector Store Selection
Gate on signals already present in the project. See `skills/database/db-comparison/SKILL.md` for the surrounding database picture.
| Signal | Choice |
|---|---|
| Project already runs PostgreSQL, corpus in the low millions of chunks | `pgvector` in the existing database — one backup, one transaction, one ops surface |
| Multi-tenant with row-level isolation already enforced in SQL | Vectors in the same database so the existing policies still apply (`skills/integrations/database-multitenancy/SKILL.md`) |
| Corpus very large, or vector search is the product's core workload | Dedicated vector database |
| Serverless / edge with no persistent database | Managed vector service from the existing cloud provider |
| Corpus small and rebuilt on deploy | In-process index — no service to operate |
Rules:
- Filters (tenant, ACL, date, type) must be applied **inside** the vector query. Post-filtering after top-k returns fewer results than requested, or none.
- Index type (exact vs approximate) is a recall/latency trade-off — measure recall against exact search on a sample before shipping approximate.
- Never store the only copy of source text in the vector store; it is a derived index and must be rebuildable from the system of record.
---
## Retrieval Patterns
| Pattern | What it fixes | Cost |
|---|---|---|
| **Hybrid search** (vector + keyword/BM25, fused) | Vector-only misses exact identifiers, codes, rare names | One extra query |
| **Metadata pre-filter** | Wrong tenant/version/language leaking into results | Requires indexed metadata |
| **Reranking** (cross-encoder over top-N) | Correct chunk retrieved but ranked too low to survive top-k | Extra latency per query |
| **Query rewriting** | Conversational or underspecified queries embed poorly | Extra model call before retrieval |
| **Parent-document retrieval** | Small chunks match well but lack context to answer | Store child→parent mapping |
| **Diversity selection (MMR)** | Top-k saturated by near-duplicate chunks | Slight relevance trade-off |
Rules:
- Retrieve wide, rerank, then pass **few** chunks to the model. Stuffing the context window degrades answer quality and multiplies cost.
- Always pass chunk identifiers into the prompt and require citations in the output — this makes hallucination detectable rather than invisible.
- When retrieval returns nothing above the similarity floor, return "no supporting information found". **Never** let the model answer from parametric memory in a grounded feature.
---
## Prompt Versioning
Prompts are deployable artifacts with behavior, not configuration strings.
| Rule | Implementation |
|---|---|
| Prompts live in version control | Dedicated files/templates in the repo, reviewed like code |
| Every prompt carries a version id | Semantic or incrementing; emitted in logs and traces with every call |
| Inputs are injected, never concatenated ad hoc | A template with named slots; user content clearly delimited |
| Model + parameters are pinned per version | Provider, model id, temperature, max tokens travel **with** the prompt version |
| Changes ship behind a flag | Roll out gradually and compare against the previous version (`skills/architecture/feature-flags/SKILL.md`) |
| Rollback is a config change | Reverting a prompt version must not require a redeploy of the whole service |
An "upgrade" of the underlying model is a **breaking change to the prompt contract**. Re-run the evaluation suite before adopting any new model version, including minor ones.
---
## Evaluation
Without an evaluation set, every prompt change is an unmeasured regression.
**Golden set** — 50–200 real, representative cases with expected outcomes. Include known failures, edge cases, adversarial inputs and out-of-scope questions. Version it with the prompts; grow it from production incidents.
Measure retrieval and generation separately — a bad answer from perfect context is a prompt problem, a bad answer from missing context is a retrieval problem.
| Layer | Metric | Meaning |
|---|---|---|
| Retrieval | Recall@k | Was the correct chunk retrieved at all? (ceiling for everything downstream) |
| Retrieval | Precision@k / MRR | How much noise reaches the prompt; how high the right chunk ranks |
| Generation | Groundedness | Is every claim supported by the retrieved context? |
| Generation | Answer relevance | Does it answer the question that was asked? |
| Generation | Format validity | Does the output parse against the expected schema, every time? |
| Operational | p95 latency, cost/request, refusal rate | Regressions users and finance feel first |
Rules:
- **Deterministic checks first** — schema validity, required citations, forbidden strings, exact-match fields. They are free and catch most regressions.
- **LLM-as-judge** is useful but biased: it favors verbose answers and its own style, and it drifts when the judge model changes. Pin the judge model + judge prompt version, and calibrate it against human labels periodically.
- Run the evaluation suite **in CI** on any change to prompts, chunking, retrieval parameters, or model version. Gate merges on the retrieval and format metrics; report the subjective ones.
- Sample real production traffic into the golden set continuously — synthetic-only suites decay.
---
## Cost and Latency Control
| Lever | Effect |
|---|---|
| Smaller/cheaper model for narrow subtasks (classification, routing, extraction) | Largest single cost reduction; reserve the strong model for synthesis |
| Provider prompt/context caching for stable prefixes | Put the invariant system prompt first, variable content last |
| Semantic or exact-match response cache for repeated queries | Removes both cost and latency entirely on hits |
| Fewer, better chunks in context | Cost scales with input tokens, latency scales with output tokens |
| Streaming responses | Perceived latency drops even when total time does not |
| `max_tokens` and output schemas | Bounds the expensive half of the bill |
| Batch/offline API for non-interactive work | Significant discount for ingestion, backfills, evaluations |
Guardrails, not suggestions:
- **Per-user and per-tenant quotas** on requests and tokens (`skills/architecture/rate-limiting/SKILL.md`). An unmetered LLM endpoint is an unbounded bill exposed to the internet.
- **Timeouts, retries with backoff, and a circuit breaker** on every provider call; degrade to a non-AI path when the provider is down (`skills/architecture/resilience/SKILL.md`).
- **Token accounting per request** persisted with tenant, feature and prompt version — cost attribution is impossible to reconstruct later.
- Long generations belong in a **background job with a result callback**, not in a synchronous request holding a connection open.
---
## Failure Modes
| Failure | Why it happens | Mitigation |
|---|---|---|
| **Hallucination** | Model completes plausibly when context is missing or contradictory | Require citations to retrieved chunk ids; verify claims map to context; return "not found" below the similarity floor |
| **Prompt injection** | Untrusted text (user input, retrieved documents, web pages, tool output) is read as instructions | Treat all non-system text as data: delimit and label it, keep authority in the system prompt, never let text grant permissions |
| **Indirect injection via RAG** | A poisoned document in the corpus hijacks every answer that retrieves it | Trust-tier the corpus, strip instruction-like patterns at ingestion, restrict which sources may be retrieved per feature |
| **Excessive agency** | Tool calls execute with the service's privileges instead of the user's | Authorize every tool call against the end user; require confirmation for destructive or irreversible actions; allowlist tools per feature |
| **PII in prompts** | User data, secrets, or full records are sent to a third party and may be retained | Minimize fields sent, redact before the call, verify provider retention/training terms, honor data-residency constraints |
| **Secret leakage into context** | Credentials in retrieved documents or env dumps get echoed back | Scan the corpus for secrets at ingestion (`skills/security/secret-management/SKILL.md`) |
| **Output injection** | Model output is rendered as HTML/markdown or executed as SQL/shell | Escape and validate model output exactly like user input — it is untrusted |
| **Silent format drift** | Free-text output parsed with regex breaks after a model update | Constrain to a schema, validate on every response, retry once with the validation error, then fail loudly |
| **Non-determinism in tests** | Same input, different output | Pin model + parameters; assert on invariants and schema, not exact strings; mock the provider in unit tests |
| **Context window overflow** | Conversation or retrieved set grows past the limit | Budget tokens explicitly per section; truncate history by summarization, not by silent tail-drop |
| **Cost blowout** | Retry storms, loops in agentic flows, unmetered endpoints | Hard caps on iterations and tokens per request; alert on cost per tenant, not just totals |
`skills/security/owasp-top-10/SKILL.md` remains fully applicable — an LLM feature is still a web feature.
---
## Observability
Emit one trace per LLM interaction, carrying: `prompt_version`, `model` + parameters, retrieved chunk ids and scores, input/output token counts, latency split (retrieval vs generation), tool calls made, and the outcome (success / schema failure / refusal / no-context).
- Log **references** to prompts and inputs, not full PII payloads; sample full traces under an explicit retention policy.
- Define SLOs on the user-visible metrics — p95 latency, error rate, refusal rate (`skills/architecture/observability-slo/SKILL.md`).
- Capture explicit and implicit user feedback (thumbs, edits, abandonment) and route it back into the golden set.
---
## Decision Checklist
- [ ] Is a model actually required, or is this a search/rules problem?
- [ ] Is the mechanism the cheapest tier that passes evaluation (prompt → few-shot → RAG → tools → fine-tune)?
- [ ] Does chunking follow document structure, and does each chunk stand alone?
- [ ] Is re-ingestion idempotent, and does deletion remove vectors?
- [ ] Is the embedding model + version recorded per row, with a re-index path?
- [ ] Are tenant/ACL filters applied inside the vector query, not after it?
- [ ] Is retrieval hybrid and reranked before hitting the context window?
- [ ] Are prompts versioned, pinned to a model, flagged, and rollback-able without a redeploy?
- [ ] Does a golden set exist, and does CI gate merges on it?
- [ ] Are quotas, timeouts, retries, token caps and cost attribution in place?
- [ ] Is every non-system text treated as data, and every model output treated as untrusted?
- [ ] Do traces carry prompt version, retrieved ids, tokens and outcome?