Skip to content
Back to skills

Build Llm Seam

ASecurity

Use when adding the first LLM call to a service, or when model calls are scattered through it — one module owns every call - model tiers from config, retries, validated structured output, streaming, prompt caching, OTel gen_ai spans with cost, versioned prompt files, PII redaction.

  • 5 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 10, 2026
developmenttypescriptpythonrustgobashnextjsnodefastapiapibackend

Works with

  • api

Security analysis

A96/100
  • mediumInstalls packages at runtime which could introduce malicious dependencies

Pro scans all 2 files and shows the line behind each finding

Scanned October 10, 2026

npx -y skills add VelimirMueller/lab-claude-skills --skill build-llm-seam --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Build Llm Seam?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Build Llm Seam
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/velimirmueller-build-llm-seam/badge)](https://www.skillsdirectory.com/skills/velimirmueller-build-llm-seam)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: build-llm-seam
description: Use when adding the first LLM call to a service, or when model calls are scattered through it — one module owns every call - model tiers from config, retries, validated structured output, streaming, prompt caching, OTel gen_ai spans with cost, versioned prompt files, PII redaction.
---

# Build the LLM Seam

Every model call in the codebase goes through one module. Call sites say *what* they want (a task, a tier, a prompt name, a schema); the seam decides *how*: which model, how long to wait, when to retry, what to log, what to redact. A vendor swap, a model upgrade or a cost cap is then a one-file change. Provider default: `ai.provider` in the profile (`anthropic`).

## 1. Audit current state

Read `.claude/stack-profile.md` (`languages`, `backend.track`, `ai.provider`, `observability.otel`, `tests`). If absent, detect from lockfiles and manifests per [stack-profile.md](../../core/_shared/stack-profile.md). Then change nothing and look:

```bash
grep -rnE "from ['\"](@anthropic-ai/sdk|openai|@google/genai)|^(import|from) (anthropic|openai)" src | grep -v "platform/llm"   # SDK use outside the seam
grep -rnE "claude-(opus|sonnet|haiku|fable)-|gpt-[0-9]|gemini-[0-9]" src | grep -v "platform/llm"                            # hard-coded model ids
grep -rnE "max_tokens|maxTokens|temperature" src | grep -v "platform/llm"                                                  # per-call knobs
ls src/platform/llm prompts config/llm.models.json 2>/dev/null
grep -rn "ANTHROPIC_API_KEY\|OPENAI_API_KEY" src | grep -v "src/config"                                                      # keys read outside config
```

Report: SDK imports and model ids outside the seam, system prompts built as strings in code, calls without a timeout or `max_tokens`, no usage logging, user text sent as-is.

## 2. Decide what to do

- No seam, no calls yet → full install (steps 4–6).
- Calls scattered → install the seam, then migrate call sites one feature at a time (step 6). The grep in step 1 is the progress meter.
- Seam present → check it against [seam-patterns.md](./seam-patterns.md) rule by rule and apply only the delta.
- Greps clean, `Verify` passes → "already in place".
- The repo is browser-only (no server): stop. A model key must not ship to a browser; add a server first ([backend skills](../../backend/_shared/service-layout.md)).

## 3. Detect track

| Signal | Branch |
|---|---|
| `languages` has `typescript`; Hono or plain Node | [llm-seam-ts.md](../_shared/llm-seam-ts.md) at `src/platform/llm/` |
| `backend.track: nextjs` | Same code at `src/server/llm/` with `import 'server-only'`; call it from Server Actions and route handlers only |
| `backend.track: fastapi` or `languages` has `python` | [llm-seam-py.md](../_shared/llm-seam-py.md) at `src/<pkg>/platform/llm/` |
| `backend.track: go` | Same shape: a `LlmAdapter` interface, one adapter file, the error taxonomy and the retry policy from [seam-patterns.md](./seam-patterns.md); use the official Go SDK in the adapter only. No canonical code here |
| `ai.provider` other than `anthropic` | Write one new adapter implementing `LlmAdapter`; nothing else changes. Map that SDK's errors to `LlmError` |

Model ids and prices differ per provider, so `config/llm.models.json` names the provider it is for.

## 4. Install only what's missing

```bash
pnpm add @anthropic-ai/sdk zod @opentelemetry/api        # TypeScript
uv add anthropic pydantic opentelemetry-api              # Python
```

Add `ANTHROPIC_API_KEY` to the service's validated config (`src/config.ts` or `config.py`, see [config.md](../../backend/_shared/config.md)): required, no default, typed as a secret. Nothing else reads it. Tracing bootstrap is already there when `observability.otel` is true ([observability.md](../../core/_shared/observability.md)); the seam only uses the API.

## 5. Generate the seams

Copy the files from the canonical page, then edit only these:

| File | Edit |
|---|---|
| `config/llm.models.json` | Provider, tier to model id, effort, prices. Verify ids and prices against [stack-versions.md](../_shared/stack-versions.md) and the live pricing page before committing |
| `prompts/<name>.md` | One file per prompt; `version` in the frontmatter; `cache: true` only for a prompt with no variables |
| `src/platform/llm/redact.ts` | Add patterns for identifiers your domain carries (customer numbers, ticket ids). Pattern redaction does not find names |

Defaults: tiers `quality` = `claude-opus-5-5`, `balanced` = `claude-sonnet-5-5`, `fast` = `claude-haiku-5-5`. `claude-fable-5-1` is not a default tier (price, 30-day retention requirement); add it as a fourth tier only after an eval shows the gain.

## 6. Wire it up

1. `main` loads the config once and builds the seam ([composition root](../_shared/llm-seam-ts.md#composition-root)); services receive `llm` as an argument.
2. Move each call site: replace the SDK call with `llm.generate({ task, tier, prompt: { name }, messages, schema, maxTokens, pii })`. Move system text into `prompts/<name>.md`. Put user-supplied and retrieved text in `messages`, never in the prompt file.
3. Ban the SDK outside the seam, so the bug cannot come back. Biome (verified on 2.5.15): `linter.rules.style.noRestrictedImports` with `paths: { "@anthropic-ai/sdk": "…" }`, turned off by an `overrides` entry for `src/platform/llm/**`. Ruff: `[lint.flake8-tidy-imports.banned-api] "anthropic".msg = "…"` plus `per-file-ignores` for `src/app/platform/llm/**` (rule `TID251`). Snippets: [seam-patterns.md](./seam-patterns.md#rule-the-sdk-is-banned-outside-the-seam).
4. Add the grep from step 1 to the task runner as `check:llm-seam`, run in CI.

## 7. Verify

```bash
pnpm typecheck && pnpm vitest run tests/llm.test.ts     # Python: uv run pyright && uv run pytest tests/test_llm.py
grep -rnE "claude-(opus|sonnet|haiku|fable)-|from ['\"]@anthropic-ai/sdk" src | grep -v platform/llm   # expect: no output
```

Expected: type-check clean; the test file shows the fake adapter receiving redacted text, a computed cost, a retry on `overloaded` and no retry on `bad_request`; the grep prints nothing.

With a real key (one paid call, a few cents), call the same cacheable prompt twice within five minutes with a prefix of at least the cache minimum. In your tracing UI the second span shows `gen_ai.usage.cache_read.input_tokens` above zero and a lower `app.llm.cost_usd`. Zero means a silent cache invalidator: see [seam-patterns.md](./seam-patterns.md#rule-cache-the-stable-prefix-and-prove-it).

## References
- [seam-patterns.md](./seam-patterns.md): the rules and why.
- [llm-seam-ts.md](../_shared/llm-seam-ts.md), [llm-seam-py.md](../_shared/llm-seam-py.md): canonical code.
- [llm-security.md](../_shared/llm-security.md): trust boundaries and the OWASP LLM Top 10 map.
- [stack-versions.md](../_shared/stack-versions.md): model ids, prices, SDK lines, verified 2026-10-09.
- [engineering-principles.md](../../core/_shared/engineering-principles.md), [observability.md](../../core/_shared/observability.md), [logging-contract.md](../../core/_shared/logging-contract.md).

Files in this skill

  • SKILL.md7 KB
  • seam-patterns.md14.3 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.