AI/ML Engineering Conventions
Rules for training pipelines, inference services, and LLM features in the house Python
stack. Models are served through FastAPI services (framework conventions in the
std-fastapi skill); general Python layout, typing, and layering live in std-python.
Stack
| Concern |
Library |
| Dataframes |
polars for new pipelines; pandas acceptable in existing code |
| Numerics |
numpy |
| Classical ML |
scikit-learn — baseline first (see below) |
| Deep learning |
PyTorch |
| Pretrained models |
transformers |
| Experiment tracking |
MLflow — tracking AND the model registry |
| Dataframe contracts |
pandera — validate at pipeline boundaries |
| Portable CPU inference |
onnxruntime |
| Embeddings store |
pgvector on the house PostgreSQL |
| LLM features |
the anthropic SDK |
- Ship a linear/tree baseline with a held-out eval before any deep learning — the
baseline tells you whether the problem needs a neural net at all, and the eval harness
you build for it outlives the baseline: every later model is judged on the same held-out
set.
- Use pgvector before adding a dedicated vector database — the ops burden of a new
datastore needs a proven ceiling first. Move only when measured recall or latency on
real corpus size demands it.
- Validate dataframes with pandera schemas at every pipeline boundary — a silent schema
drift upstream becomes a silent quality drop downstream.
Notebooks vs Modules
notebooks/ is for exploration only — plots, one-off analysis, scratch work.
- Promote production logic into
src/ modules and import it into the notebook —
never import from a notebook: notebooks have no tests, no types, no review gate,
and hidden execution-order state.
- Strip outputs before commit (
nbstripout as a git filter) — outputs bloat diffs and
leak data samples into the repo.
Reproducibility
- The uv lockfile pins the environment — a training run is only reproducible if its
dependencies are.
- Seed
random, numpy, and torch in one set_seed(seed) helper called at process start —
scattered seeding is unverifiable seeding.
- Training runs are config-driven via pydantic models (one
TrainingConfig, loaded from a
file or CLI) — no hand-edited hyperparameters buried in code; the config is logged with
the run, so the run can be re-created from it.
- Never train against a mutable "latest" table — datasets are versioned snapshots
(DVC, or versioned S3 parquet paths). A model trained on data nobody can reproduce is a
model nobody can debug.
Experiment Tracking (MLflow)
- Every training run logs params, metrics, and artifacts to MLflow — an unlogged run may
as well not have happened.
- Promote models through registry aliases (
@staging → @production), never by copying
artifacts around by hand — the registry is the single source of "what is deployed".
- No model ships without a recorded eval on a pinned dataset — a metric on an
unpinned dataset cannot be compared to the previous model's, so it cannot justify a
promotion.
Serving (FastAPI)
- Load the model once in the FastAPI lifespan — never per-request: model load is
seconds and hundreds of MB; per-request loading turns every call into a cold start.
- Pin the model artifact version in config (a registry alias or explicit version) — a
service that loads "latest" changes behavior without a deploy.
- The health endpoint reports the loaded model version — so an operator can see at a
glance which model each instance is actually serving.
- Set request timeouts and a max payload size — inference endpoints are a
denial-of-service magnet without both.
- Watch p95 inference latency — dashboards and alerting conventions are owned by
std-monitoring.
LLM Integration (anthropic SDK)
- Model choice:
claude-sonnet-5 by default for product features;
claude-haiku-4-5-20251001 for cheap high-volume classification; claude-opus-5 when
reasoning depth is the product. Pin the model id in config, not inline.
- Prompts are versioned code — files in the repo, reviewed in PRs — not database strings:
a prompt change is a behavior change and gets the same diff, review, and rollback.
- Structured outputs come from tool use, not from parsing prose — a tool schema is a
contract; a regex over prose is a hope.
- A pinned eval set runs in CI before a prompt or model change ships — LLM behavior
shifts across prompts and model versions, and an eval is the only regression test that
catches it.
- Retry transient API errors with exponential backoff and a capped attempt count (the
SDK's built-in retries or
tenacity).
- Never log full prompts or completions containing user PII — log request ids, token
counts, latency, and model id instead; logging rules are owned by
std-monitoring.
- Give every LLM feature a cost budget (per-request and monthly) and review it like a
performance budget — token spend regresses as silently as latency does.
Embeddings (pgvector)
- Pin the embedding model and its dimension together in config — vectors from different
models share a column type but not a space; mixing them silently ruins retrieval.
- Index with HNSW by default — better recall/latency than IVFFlat at house corpus sizes.
- Changing the embedding model means re-embedding the entire corpus — plan it as a
migration (dual-write or backfill, then cut over), not an in-place swap.
Rollout
- New models ship behind a flag with shadow or canary traffic first — offline eval
numbers do not guarantee online behavior.
- Monitor input drift and prediction distributions in production; alert on divergence
from the training distribution — models fail silently by degrading, not by crashing.
Related, owned elsewhere — do not duplicate: the JSON error envelope and pagination
response format live in std-api-design; migration safety and indexing depth in
std-database; OWASP and secret management in std-security; structured logging and
PII-in-logs rules in std-monitoring; AAA and coverage targets in std-testing; general
Python layout, typing, and layering in std-python; ORM query performance in
std-python-performance; FastAPI framework specifics in std-fastapi.
1---2name: std-python-ai-ml3description: AI/ML engineering conventions — notebooks vs modules, MLflow experiment tracking, reproducibility, FastAPI model serving, pgvector embeddings, LLM (Anthropic) integration, evals. Use when writing training pipelines, inference services, or LLM features.4---56# AI/ML Engineering Conventions78Rules for training pipelines, inference services, and LLM features in the house Python9stack. Models are served through FastAPI services (framework conventions in the10`std-fastapi` skill); general Python layout, typing, and layering live in `std-python`.1112## Stack1314| Concern | Library |15|---------|---------|16| Dataframes | **polars** for new pipelines; pandas acceptable in existing code |17| Numerics | numpy |18| Classical ML | scikit-learn — **baseline first** (see below) |19| Deep learning | PyTorch |20| Pretrained models | transformers |21| Experiment tracking | MLflow — tracking AND the model registry |22| Dataframe contracts | pandera — validate at pipeline boundaries |23| Portable CPU inference | onnxruntime |24| Embeddings store | pgvector on the house PostgreSQL |25| LLM features | the `anthropic` SDK |2627- **Ship a linear/tree baseline with a held-out eval before any deep learning** — the28 baseline tells you whether the problem needs a neural net at all, and the eval harness29 you build for it outlives the baseline: every later model is judged on the same held-out30 set.31- **Use pgvector before adding a dedicated vector database** — the ops burden of a new32 datastore needs a proven ceiling first. Move only when measured recall or latency on33 real corpus size demands it.34- Validate dataframes with pandera schemas at every pipeline boundary — a silent schema35 drift upstream becomes a silent quality drop downstream.3637## Notebooks vs Modules3839- `notebooks/` is for exploration only — plots, one-off analysis, scratch work.40- Promote production logic into `src/` modules and import it into the notebook —41 **never import from a notebook**: notebooks have no tests, no types, no review gate,42 and hidden execution-order state.43- Strip outputs before commit (`nbstripout` as a git filter) — outputs bloat diffs and44 leak data samples into the repo.4546## Reproducibility4748- The uv lockfile pins the environment — a training run is only reproducible if its49 dependencies are.50- Seed `random`, numpy, and torch in one `set_seed(seed)` helper called at process start —51 scattered seeding is unverifiable seeding.52- Training runs are config-driven via pydantic models (one `TrainingConfig`, loaded from a53 file or CLI) — no hand-edited hyperparameters buried in code; the config is logged with54 the run, so the run can be re-created from it.55- **Never train against a mutable "latest" table** — datasets are versioned snapshots56 (DVC, or versioned S3 parquet paths). A model trained on data nobody can reproduce is a57 model nobody can debug.5859## Experiment Tracking (MLflow)6061- Every training run logs params, metrics, and artifacts to MLflow — an unlogged run may62 as well not have happened.63- Promote models through registry aliases (`@staging` → `@production`), never by copying64 artifacts around by hand — the registry is the single source of "what is deployed".65- **No model ships without a recorded eval on a pinned dataset** — a metric on an66 unpinned dataset cannot be compared to the previous model's, so it cannot justify a67 promotion.6869## Serving (FastAPI)7071- **Load the model once in the FastAPI lifespan — never per-request**: model load is72 seconds and hundreds of MB; per-request loading turns every call into a cold start.73- Pin the model artifact version in config (a registry alias or explicit version) — a74 service that loads "latest" changes behavior without a deploy.75- The health endpoint reports the loaded model version — so an operator can see at a76 glance which model each instance is actually serving.77- Set request timeouts and a max payload size — inference endpoints are a78 denial-of-service magnet without both.79- Watch p95 inference latency — dashboards and alerting conventions are owned by80 `std-monitoring`.8182## LLM Integration (anthropic SDK)8384- Model choice: `claude-sonnet-5` by default for product features;85 `claude-haiku-4-5-20251001` for cheap high-volume classification; `claude-opus-5` when86 reasoning depth is the product. Pin the model id in config, not inline.87- Prompts are versioned code — files in the repo, reviewed in PRs — not database strings:88 a prompt change is a behavior change and gets the same diff, review, and rollback.89- Structured outputs come from tool use, not from parsing prose — a tool schema is a90 contract; a regex over prose is a hope.91- **A pinned eval set runs in CI before a prompt or model change ships** — LLM behavior92 shifts across prompts and model versions, and an eval is the only regression test that93 catches it.94- Retry transient API errors with exponential backoff and a capped attempt count (the95 SDK's built-in retries or `tenacity`).96- **Never log full prompts or completions containing user PII** — log request ids, token97 counts, latency, and model id instead; logging rules are owned by `std-monitoring`.98- Give every LLM feature a cost budget (per-request and monthly) and review it like a99 performance budget — token spend regresses as silently as latency does.100101## Embeddings (pgvector)102103- Pin the embedding model and its dimension together in config — vectors from different104 models share a column type but not a space; mixing them silently ruins retrieval.105- Index with HNSW by default — better recall/latency than IVFFlat at house corpus sizes.106- Changing the embedding model means re-embedding the entire corpus — plan it as a107 migration (dual-write or backfill, then cut over), not an in-place swap.108109## Rollout110111- New models ship behind a flag with shadow or canary traffic first — offline eval112 numbers do not guarantee online behavior.113- Monitor input drift and prediction distributions in production; alert on divergence114 from the training distribution — models fail silently by degrading, not by crashing.115116Related, owned elsewhere — do not duplicate: the JSON error envelope and pagination117response format live in `std-api-design`; migration safety and indexing depth in118`std-database`; OWASP and secret management in `std-security`; structured logging and119PII-in-logs rules in `std-monitoring`; AAA and coverage targets in `std-testing`; general120Python layout, typing, and layering in `std-python`; ORM query performance in121`std-python-performance`; FastAPI framework specifics in `std-fastapi`.