# `PhoenixKitAI.TaskSpec`
[🔗](https://github.com/BeamLabEU/phoenix_kit_ai/blob/v0.26.1/lib/phoenix_kit_ai/task_spec.ex#L1)

The declaration of an AI task: a named use of AI that a module's code
makes, returned by the module's `ai_tasks/0` callback and found by
`PhoenixKitAI.Tasks`.

    def ai_tasks do
      [
        %PhoenixKitAI.TaskSpec{
          key: "warehouse.inventory_photo",
          label: gettext_noop("Inventory by photo"),
          gettext_backend: MyModule.Gettext,
          purpose: gettext_noop("Count the stock items visible in shelf photos."),
          needs: [:vision, :json_schema],
          endpoints: {MyModule.Settings, :inventory_endpoint_uuids, []}
        }
      ]
    end

The key is a string prefixed with the module's key. `needs` says what the
model has to be able to do: `:vision` (images in), `:file_input`
(documents such as PDF in), `:image_output` (it generates images),
`:tools`, `:json_schema` (a strict JSON schema) — hard requirements a
call is refused for — and `:json` (any JSON; soft, only noted). `endpoints` is an
`{module, function, args}` returning the endpoints the task uses now (uuids
or `%PhoenixKitAI.Endpoint{}`s, a single one, `{:ok, uuid}` or `nil`).

`default_limits` are the task's limits when an admin has set none
(`%{task: %{unit => %{period => amount}}, user: …}`, units `:money` in
dollars — a `Decimal` or a dollar string like `"20.00"`, never an integer,
which the released versions read as millionths and which now refuses the
task's calls —, `:requests`, `:tokens`, periods `:day`, `:month` — see
`PhoenixKitAI.TaskLimits`). `watchers` is an `{module, function, args}`
called with the user uuid (or nil) appended, returning the uuids of more
people to tell about the task's limits (`PhoenixKitAI.GuardEvents`); the
user is never told as a watcher.

`user_limits` (default `true`) says whether the guard limits each user in
the task. `false` is for a task whose module keeps its own limits per
user: `PhoenixKitAI.TaskLimits` then has only the task's limits for it,
ignores the user part of `default_limits` and the user values an admin
stored (they stay in the database), refuses to set user limits
(`{:error, :user_limits_off}`), and «AI → Tasks» shows no user limits for
it. Only `false` turns them off: `PhoenixKitAI.Tasks` reads any other
value (`nil` included) as `true`.

`self_flag` says the consumer has its model flag off-purpose requests
(the default of the task's guard mode, `PhoenixKitAI.Guard.self_flag?/1`).
`private` hides the reasons of the task's flags and blocks from all but
the holders of `ai.manage`. `block_group` names the group a block of the
task lands on (nil: the task's own key; `"free_text"` for tasks that take
free text). `sources` (the `source` values a call may come from; a
module name allows its functions), `max_calls_per_hour` (per person) and
`max_tokens_per_call` are norms whose breach is signalled, never blocked
(`PhoenixKitAI.Guard.check_anomalies/1`). `auto_block_threshold` and
`content` are kept for a later part of the guard (a classifier); nothing
reads them yet. `typical_tokens` (`{input, output}` of a typical call) is
read by `PhoenixKitAI.Recommendations` to estimate the price of a call.

`module` and `enabled` are filled in by `PhoenixKitAI.Tasks`: the module
that declared the task, and whether that module is switched on.

# `mfa_ref`

```elixir
@type mfa_ref() :: {module(), atom(), list()}
```

# `need`

```elixir
@type need() :: :vision | :file_input | :image_output | :tools | :json_schema | :json
```

# `t`

```elixir
@type t() :: %PhoenixKitAI.TaskSpec{
  auto_block_threshold: pos_integer() | nil,
  block_group: String.t() | nil,
  content: mfa_ref() | nil,
  default_limits: map(),
  enabled: boolean(),
  endpoints: mfa_ref() | nil,
  gettext_backend: module() | nil,
  key: String.t(),
  label: String.t() | nil,
  max_calls_per_hour: pos_integer() | nil,
  max_tokens_per_call: pos_integer() | nil,
  module: module() | nil,
  needs: [need()],
  private: boolean(),
  purpose: String.t() | nil,
  self_flag: boolean(),
  sources: [String.t()] | nil,
  typical_tokens: {non_neg_integer(), non_neg_integer()} | nil,
  user_limits: boolean(),
  watchers: mfa_ref() | nil
}
```

# `hard_needs`

```elixir
@spec hard_needs() :: [need()]
```

The requirements a call is refused for when the model lacks them.

# `label`

```elixir
@spec label(t()) :: String.t()
```

The task's label in the current locale, through its own gettext backend
when it names one; the key when it has no label.

# `needs`

```elixir
@spec needs() :: [need()]
```

Every requirement a task or a call can have.

# `purpose`

```elixir
@spec purpose(t()) :: String.t() | nil
```

The task's purpose for display, translated through the declaring module's
`gettext_backend` when one is set (declare it with `gettext_noop/1`). The
untranslated text is what prompts and classifiers get, so their wording
stays the same in every locale.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
