# Embeddings and recordings

The full rules for `ai.embed`, `ai.embed_all` and recorded answers. The
common case is under [Asking for an embedding](asking-for-an-embedding.md)
and [Recorded answers](ai-tests.md#recorded-answers).

- **`embedding_dimensions:`** is a whole number from 1 to 16,000,
  pgvector's most, written out (`ai.settings_invalid` otherwise). Every
  `ai.embed` and `ai.embed_all` call needs it: an app that embeds without
  it is `semantic.ai_dimensions_mismatch` at the call's `as:`, as one
  whose type has another length is. The built-in driver doesn't send it,
  because a local model makes vectors of its own length and servers
  differ on the `dimensions` parameter. Instead, each vector the model
  answers with is read as `V`, so one of another length is
  `misconfigured`. A package whose provider can shorten its vectors
  sends it, through its mapping's `embed:`.
- **What `ai.embed` takes.** The text is a value built on `Text`, or a
  `Markdown`, never one that may be `none`; `embed_all` takes a `List` of
  them (`type.mismatch` otherwise). `as:` is a type written out built on
  `Vector` with `dimensions:` (`type.mismatch` for a plain `Vector`, an
  `Embedding` or anything else). The options are `model:`, a name `ai: {
  models: }` gives (`semantic.unknown_name`), defaulting to `embedding`,
  which `ai:` must then name (`ai.settings_invalid` at the call), and
  `timeout:`, `1s` to `10m`, 5 seconds unless written. Any other is
  `type.named_argument_unknown`. Each of `timed_out`, `over_budget` and
  `too_long` needs an arm naming an `unavailable` failure, as
  `ai.generate`'s conditions do. Only an action calls it.
- **The built-in driver** sends OpenAI's embeddings request, `POST
  <url>/embeddings` with `model` and `input`, every text in one request,
  and reads `data[i].embedding` in `index` order. A `400` whose
  `error.code` is `context_length_exceeded` is `too_long`; no answer
  within `timeout:` is `timed_out`; the rest is read as `ai.generate`
  reads it (`misconfigured`, `unavailable`, sent again within
  `timeout:`). More than 100 texts in one `embed_all` is `too_long`, with
  nothing sent; no texts gives no vectors without asking. The call is
  logged and recorded as the operation `embed` of the model connection,
  and its answer reused on a rerun and kept as a step of a record, as
  `ai.generate`'s is.
- **The fake's embedding** of a text is `dimensions` numbers drawn from
  SHA-256 over the text's own SHA-256, the test's seed and a counter, each
  the top 24 bits of four bytes scaled to -1 to 1, then divided by the
  vector's length, so the same text under one seed always gives the same
  unit vector, and two texts give vectors far apart. `fakes: { ai.embed:
  [...] }` scripts only the three failures, each one call of `ai.embed` or
  `ai.embed_all` in the order made (`ai.embed_all` is scripted as
  `ai.embed`): a vector written out in a test would be as long as the
  model's, and embedding the same text gives the same vector anyway. A
  prompt called `embed` is scripted as the operation.
- **A recording** is `recordings/<prompt>/<name>.json` in the prompt's
  own `prompts/` folder: one JSON object with `answer`, a value of the
  type the prompt is asked for as written as `json.write` writes it, and
  `version`, the prompt's version it answered, `answer_question@864ad76b`.
  The answer keeps plain texts and numbers as placeholders naming their
  type, as a package's [recordings](recordings.md) do,
  so no personal data a prompt carried is committed; booleans, enum spellings, lists' lengths
  and `null` are kept as given. A placeholder is filled with a generated
  value of its type, at its place, each time the recording is read.
- **`recorded("<name>")`** in `fakes: { ai.<prompt>: }` names one of the
  prompt's recordings, written out (`semantic.fake_invalid` with the
  nearest otherwise), and is one attempt answered with its answer, sent
  as the model sends one, so a strict mode's `value` wrapping and a
  `Text` answer's plain text are as a live answer's, and decoded by the
  call's own type.
- **Every recording is decoded by `jadpo test`,** the build the gate
  runs. Each is a test of the `recording` kind, as a package's
  recordings are, named `<prompt>'s <name> recording decodes`, through
  each type an `ai.generate` call asks its prompt for as. A recording
  fails it as `test.recording_undecodable` when it doesn't decode, isn't
  one JSON object with an `answer` and a `version`, sits where no prompt
  of its folder's name is, or records a prompt no call asks.
- **A changed prompt's recordings** are an alert, `▲ alert`, on
  `jadpo test`'s report, and in its `--json` as `data.alerts`
  (`kind: recording_stale`). The one hint after the footer points at
  `jadpo ai show`. Jadpo has no warning severity, and a stale recording
  still decodes, so neither `check` nor the run fails on it.
