ai-server
Skill525 starsChanged 2 months agoApache-2.0View on GitHub
Conventions for the opentrons-ai-server FastAPI service — project structure, uv dependency management, settings, testing, Docker, and deployment. Use when working with files in opentrons-ai-server/ or discussing the AI server API.
- Reads credentials
We split this skill into the rules it gives an agent
12 rules. About 0 are common, 1 is distinctive, and 11 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈1
Distinctive · estimate
This author's own choices. Worth a look.
11
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 25
All endpoints require a Bearer token in Authorization.
- Very specific: line 49
After changing deps, commit both pyproject.toml and uv.lock.
- Very specific: line 79
All runtime configuration lives in api/settings.py via pydantic-settings:
- Very specific: line 83
Every new env var or secret must be added as a field on the Settings class
- Very specific: line 85
Always import settings via the get_settings() singleton (not Settings() directly) to avoid re-parsing .env on every import
- Very specific: line 97
All config is in pyproject.toml — no separate config files:
- Very specific: line 109
All targets run from opentrons-ai-server/.
- Very specific: line 171
Test helpers in tests/helpers/ handle Auth0 token caching and HTTP client setup
- Very specific: line 183
Import sorting handled by ruff's I rule (isort-compatible)
- Very specific: line 187
Add the field to the Settings class in api/settings.py (use SecretStr for secrets)
- Very specific: line 188
Add the value to your local .env file
- Distinctive: line 189
Before deploying: add the value in AWS Secrets Manager under the environment's secret name
≈ “Common” is an estimate: at least 1 in 5 of the 308,232 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
AI Server Instructions
Overview
opentrons-ai-server is a standalone FastAPI service for Opentrons AI — protocol generation, chat completions, and related AI features. It is not part of the monorepo build system; it has its own dependency management, CI workflows, and deployment pipeline.
Deployed environments: staging (staging.opentrons.ai) and prod (ai.opentrons.com), running on AWS ECS Fargate behind CloudFront.
API Endpoints
The server exposes four chat endpoints that return JSON responses:
| Endpoint | Purpose |
POST /api/chat/completion | General chat (no file attachments) |
POST /api/chat/completion-multipart | Chat with file attachments (multipart form) |
POST /api/chat/create-protocol | Generate a new protocol |
POST /api/chat/update-protocol | Update an existing protocol |
All endpoints require a Bearer token in Authorization. Setting "fake": true in the request body bypasses the LLM and returns a canned response from api/domain/fake_responses.py — useful for local development without Anthropic API calls.
Package Manager — uv
This project uses uv for Python dependency management (not pipenv, pip-tools, or poetry).
| File | Role | Committed? |
pyproject.toml | Single source of truth for dependencies AND all tool config | Yes |
uv.lock | Locked dependency graph | Yes |
requirements.txt | Unused. Docker installs from uv.lock directly | No (gitignored) |
.venv/ | Local virtual environment created by uv sync | No (gitignored) |
Key Commands
make setup # Install all deps (uv sync --frozen)
uv add <package> # Add production dep
uv add --dev <package> # Add dev-only dep
uv remove <package> # Remove dep
uv lock # Re-resolve after manual pyproject.toml edits
uv run <command> # Run inside the managed venvAfter changing deps, commit both pyproject.toml and uv.lock.
Project Structure
opentrons-ai-server/
├── api/ # Application source code
│ ├── handler/ # FastAPI app, routes, middleware (fast.py entrypoint)
│ ├── domain/ # Business logic — LLM prediction (Anthropic, OpenAI)
│ ├── models/ # Pydantic request/response models
│ ├── services/ # File processing and other services
│ ├── integration/ # External integrations (Auth0, Google Sheets, AWS)
│ ├── constants/ # Shared constants
│ ├── data/ # Static data files
│ ├── storage/ # Stored API docs, indexes
│ ├── utils/ # API docs sync, curation, metadata helpers
│ └── settings.py # Pydantic Settings — all env vars and secrets
├── tests/
│ ├── conftest.py # Pytest fixtures and --env option
│ ├── helpers/ # Client, token helpers for live testing
│ └── test\_\*.py # Unit and live tests
├── deploy.py # ECS Fargate deployment script
├── Dockerfile
├── Makefile
├── pyproject.toml
└── uv.lockConfiguration & Settings
All runtime configuration lives in api/settings.py via pydantic-settings:
- Locally: values come from a
.envfile (gitignored) - Deployed: values come from AWS Secrets Manager, loaded into ECS by
deploy.py - Every new env var or secret must be added as a field on the
Settingsclass - Secrets use
SecretStrtype; non-secret vars are plain strings with defaults - Always import settings via the
get_settings()singleton (notSettings()directly) to avoid re-parsing.envon every import
Notable settings:
allowed_origins— comma-separated CORS origins (must be explicit; wildcard*is invalid withallow_credentials=True)request_timeout_seconds— request timeout in seconds (default"300"); production proxies must be configured to allow at least this durationanthropic_max_tokens— stored as a string, cast tointwhen used
Generate a template .env from defaults: make gen-env
Tool Configuration
All config is in pyproject.toml — no separate config files:
| Tool | Section | Purpose |
| ruff | [tool.ruff], [tool.ruff.lint], [tool.ruff.format] | Linting AND formatting |
| mypy | [tool.mypy], [[tool.mypy.overrides]] | Strict type checking with pydantic plugin |
| pytest | [tool.pytest.ini_options] | Test runner config, markers: unit, live |
Line length: 140. Target: Python 3.12. Mypy is in strict mode.
Makefile Targets
All targets run from opentrons-ai-server/.
Development
| Target | Description |
make setup | Install all deps (uv sync --frozen --extra dev) |
make teardown | Delete .venv/ |
make format | Auto-fix lint + format with ruff, then prettier for .md/.json |
make lint | Check lint (ruff) + type check (mypy) — no auto-fix |
make prep | format then lint then unit-test |
make unit-test | Run unit tests (pytest tests -m unit) |
Running Locally
| Target | Description |
make local-run | Run FastAPI with uvicorn (hot reload, no Docker) |
make build | Sync API docs, then build the Docker image |
make run | Run the Docker container (requires .env file) |
make rebuild | clean + build + run |
make live-test | Run live tests against a running server (ENV=local default) |
make live-client | Interactive client for testing the API |
Deployment
| Target | Description |
make deploy ENV=staging | Build, push to ECR, update ECS service |
make dry-deploy ENV=staging | Retrieve AWS data but make no changes |
make build-only ENV=staging | Build Docker image only, no push/deploy |
Docker Build
The image is a two-stage build. The dependency stage copies a digest-pinned uv 0.11.17 binary and runs uv sync --frozen --no-dev --no-install-project, so packages come from uv.lock with their recorded hashes. The runtime stage copies only that virtualenv and api/; it does not contain uv.
make build syncs the Python API docs, then builds. The Docker build context is the repo root (not opentrons-ai-server/). Docs come from the pinned DOCS_TAG Makefile variable and must be present under api/storage/api_docs/docs/v2.
Entrypoint: uvicorn api.handler.fast:app (3 workers, port 8000).
Python API docs curation
The helper model that selects relevant docs reads api/storage/api_docs/api_docs_struct.md. Rich <about> routing text is not taken from synced markdown alone; it comes from committed curation.
| File | Edit? | Purpose |
api_docs_struct_about.md | Yes | Source of truth for curated <about> text |
api_docs_struct.md | No | Generated on make sync-api-docs |
docs/v2/ (synced markdown) | No | Gitignored; fetched from DOCS_TAG |
make sync-api-docs # regenerate api_docs_struct.md from curated about file
make check-api-docs-curation # fail if curated entries and synced docs divergeFull details: opentrons-ai-server/docs/API_DOCS_CURATION.md
Testing
- Unit tests (
@pytest.mark.unit): run offline →make unit-test - Live tests (
@pytest.mark.live): run against a real server →make live-test ENV=local - The
--envpytest option selects the target environment (local/staging/prod) - Test helpers in
tests/helpers/handle Auth0 token caching and HTTP client setup
Authentication
Auth0 JWT verification via api/integration/auth.py. Config: auth0_domain, auth0_api_audience, auth0_issuer, auth0_algorithms in Settings.
Code Conventions
- Formatting and linting: ruff only (no black).
make formatto auto-fix - Type annotations: required everywhere — mypy strict mode
- Pydantic models for all request/response schemas (in
api/models/) - Structured logging via
structlog - Import sorting handled by ruff's
Irule (isort-compatible)
Adding a New Env Var or Secret
- Add the field to the
Settingsclass inapi/settings.py(useSecretStrfor secrets) - Add the value to your local
.envfile - Before deploying: add the value in AWS Secrets Manager under the environment's secret name
- Re-deploy — the deploy script maps Settings fields to ECS container env vars automatically
In this repository18 other files it gives its agents.
- .cursor/rules/ai-server-api-docs-curation.mdcCursor rule
- .cursor/rules/monorepo-setup.mdcCursor rule
- ai-clientSkill
- analyses-snapshot-testingSkill
- css-modulesSkill
- docsSkill
- documented-mutationsSkill
- e2e-testingSkill
- grill-frontend-codeSkill
- js-package-testingSkill
- locize-syncSkill
- opentrons-typescriptSkill
- protocol-authoringSkill
- protocol-designerSkill
- protocol-verificationSkill
- react-component-creationSkill
- robot-python-projectsSkill
- static-deploySkill
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
Reviews can't be read right now.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

