Packages

Use LLM judgment in regular Elixir control flow.

Current section

Files

Jump to
gut README.md
Raw

README.md

# Gut
Use LLM judgment in regular Elixir control flow. Gut turns subjective input into a fixed value your application can route, match, store, and act on.
- Route tickets, leads, and reports to the right workflow.
- Classify intent, sentiment, topic, or risk.
- Score urgency, quality, or severity on a bounded scale.
- Automate clear cases and send uncertain work to a person.
```elixir
ticket = %{
subject: "Charged twice for the same order",
body: "Please refund the second charge."
}
Gut.feel(ticket, "Which team should handle this?",
billing: "Payments, invoices, and refunds",
technical: "Product defects and access problems",
other: "Anything else"
)
#=> {:ok, :billing}
```
Gut gives the model a fixed set of choices, then maps its answer back to the original value. Your application gets `:billing`, not a generated string that it still needs to parse.
## What you can do
Use Gut when the input is subjective but the result must be concrete:
```elixir
Gut.feel(review, "What is the sentiment?", [:positive, :neutral, :negative])
#=> {:ok, :positive}
Gut.feel(messages, "How frustrated is the customer?", 1..5)
#=> {:ok, 4}
Gut.feel(report, "Does this need manual review?", [true, false])
#=> {:ok, true}
```
A successful result means the provider returned one of your choices. It does not mean the judgment is correct.
## Installation
Add `gut` and `req_llm` to your dependencies:
```elixir
def deps do
[
{:gut, "~> 0.1.0"},
{:req_llm, "~> 1.24"}
]
end
```
Configure a model. For example, use a Jev evaluation model:
```elixir
config :gut,
adapter: {Gut.ReqLLM.Jev, model: "typesafe:jev-latest"}
```
Or use `Gut.ReqLLM` with any LLM model supported by ReqLLM:
```elixir
config :gut,
adapter: {Gut.ReqLLM, model: "anthropic:claude-haiku-4-5"}
```
Options after `:model` pass through to `ReqLLM.generate_text/3`.
## Choices
Pass a list when each value explains itself:
```elixir
Gut.feel(email, "Is this spam?", [true, false])
```
Pass keyword choices when the model needs more context:
```elixir
Gut.feel(ticket, "Which team should handle this?",
billing: "Payment, invoice, or refund problems",
technical: "Product defects or access problems",
other: "Anything else"
)
```
Pass an integer range for a bounded score:
```elixir
Gut.feel(messages, "How frustrated is the customer?", 1..5)
```
Gut returns the selected list value, keyword key, or range member. Choices must be unique and contain no more than 100 values.
Use `Gut.feel!/4` when a provider failure should raise:
```elixir
team = Gut.feel!(ticket, "Which team should handle this?", [:billing, :technical])
```
## Subjects
Gut sends strings as plain text and encodes other values as JSON.
Derive `Gut.Subject` to control which struct fields leave your application:
```elixir
defmodule Ticket do
@derive {Gut.Subject, only: [:subject, :body]}
defstruct [:subject, :body, :internal_notes]
end
```
Prefer `:only` so new fields are not sent by accident. You can also implement `Gut.Subject` when a subject needs a custom text representation.
Gut treats the subject as untrusted data in its prompt. This reduces accidental prompt confusion, but it does not prevent prompt injection.
## Errors
`Gut.feel/4` returns `{:error, %Gut.Error{}}` for provider and adapter failures. The `reason` field is stable enough for control flow:
- `:timeout`
- `:rate_limited`
- `:unauthorized`
- `:invalid_answer`
- `:adapter_error`
The `cause` field keeps the original error for logging. Invalid local input raises `ArgumentError` before the adapter runs.
## Testing
Use `Gut.Test` in `config/test.exs` to keep tests deterministic and prevent LLM requests:
```elixir
config :gut, adapter: Gut.Test
```
It selects the first choice by default. Set a zero-based index when a test needs another result:
```elixir
config :gut, adapter: {Gut.Test, index: 1}
```
`Gut.Test` has no shared state, so tests can use `async: true`. Mock your application's domain boundary when individual tests need different judgments.
## Custom adapters
Implement the `Gut.Adapter` behaviour to use another provider or evaluation system. Configure the module globally or for one call:
```elixir
Gut.feel(subject, question, choices, adapter: MyApp.GutAdapter)
```
## Development
Gut requires Elixir 1.18 or later.
```sh
mix deps.get
pre-commit install
```
Run the checks:
```sh
mix format --check-formatted
mix compile --warnings-as-errors
mix credo --strict
mix test
```