Install

Add Jibu to your dependencies in mix.exs:

{:jibu, "~> 0.2"}

Create a client

Jibu.new/1 returns a Req.Request. Build it once and reuse it.

client = Jibu.new(api_key: System.fetch_env!("ANTHROPIC_API_KEY"))

Without :api_key, Jibu reads the ANTHROPIC_API_KEY environment variable. Beta features need their names in :betas:

client = Jibu.new(betas: ["some-beta-2026-01-01"])

Set the model and max_tokens once on the client:

client = Jibu.new(model: "claude-opus-5-5", max_tokens: 1024)

Jibu.messages/2 and Jibu.count_tokens/2 apply these defaults. A field set in the body wins. Batch requests do not get them. :defaults takes further fields, for example defaults: %{temperature: 0}. Every other option goes to Req.merge/2, for example :receive_timeout.

Ask a question

Jibu.ask/3 sends a prompt and returns the text of the answer.

{:ok, text} = Jibu.ask(client, "Name three rivers in Germany.")

The prompt is a string or a list of content blocks, see Images, documents and files. These shortcuts have a fixed meaning:

  • system: sets the system prompt. cache: true marks it for prompt caching.
  • effort: sets output_config.effort.
  • messages: passes an earlier conversation. The prompt is appended to it.
  • on_text: streams the answer and passes each piece of text to a function. See below.

Every other keyword becomes a body field unchanged.

{:ok, text} =
  Jibu.ask(client, "And the longest of them?",
    system: "Answer in one sentence.",
    effort: :low,
    temperature: 0,
    messages: [
      %{role: "user", content: "Name three rivers in Germany."},
      %{role: "assistant", content: "Rhine, Elbe and Danube."}
    ]
  )

A refusal and output cut off at max_tokens return {:error, %Jibu.Error{}}. So does a turn that waits for a tool. Use Tools for those.

on_text: shows the answer while it arrives, for example in a LiveView:

pid = self()
{:ok, text} = Jibu.ask(client, "Write a haiku.", on_text: &send(pid, {:text, &1}))

The function sees the text before ask/3 knows whether the answer succeeded. After a refusal, max_tokens, an error event or a lost connection, it may already have received text. ask/3 then returns the error. For every stream event, use Jibu.stream/3.

Send a full request

Jibu.messages/2 takes the whole body as a plain map and returns the whole response. Jibu sends the body unchanged, apart from the client defaults.

{:ok, response} =
  Jibu.messages(client, %{
    model: "claude-opus-5-5",
    max_tokens: 1024,
    messages: [%{role: "user", content: "Name three rivers in Germany."}]
  })

Jibu.Response.text(response)

Jibu.Response lifts id, model, content, stop_reason and usage from the reply. response.body holds the complete decoded JSON with string keys.

Check the stop reason

stop_reason tells you why the model stopped.

  • "end_turn": the answer is complete.
  • "max_tokens": the answer was cut off. Raise max_tokens or shorten the task.
  • "refusal": the model declined. The content is not an answer.
  • "tool_use": the model asks for a tool. See Tools.

Set the effort

output_config.effort trades depth of reasoning against tokens and latency. With ask/3 and extract/4, the effort: shortcut sets it.

Jibu.messages(client, %{
  model: "claude-opus-5-5",
  max_tokens: 1024,
  output_config: %{effort: "low"},
  messages: messages
})

Use "low" for classification and short extraction. Measure before raising it.

Count tokens

Jibu.count_tokens/2 takes the same body as Jibu.messages/2. The API does not bill it. Of the client defaults, only the model applies here.

{:ok, %{"input_tokens" => count}} = Jibu.count_tokens(client, body)

List models

{:ok, model} = Jibu.Models.get(client, "claude-opus-5-5")
client |> Jibu.Models.stream() |> Enum.map(& &1["id"])

Test without the network

Pass a Req.Test plug as a client option. No request leaves the test process.

client = Jibu.new(api_key: "test", plug: {Req.Test, MyApp.Claude}, retry: false)

Req.Test.stub(MyApp.Claude, fn conn ->
  Req.Test.json(conn, %{
    "content" => [%{"type" => "text", "text" => "Hello"}],
    "stop_reason" => "end_turn"
  })
end)

Req.Test needs the plug package in your test dependencies.