Packages

Elixir client for the Anthropic Messages API — typed content blocks, native tool use, streaming, retries.

Current section

9 Versions

Jump to

Compare versions

12 files changed
+183 additions
-209 deletions
  @@ -1,3 +1,17 @@
1 + ## 0.4.1 - 2024-03-13
2 +
3 + ### Improved
4 + - Removed GenServer from `Anthropic.Config`. It will be created from `Application.get_env` or from the supplied options.
5 + - Added Mox to test environment.
6 + - Increased test coverage.
7 +
8 + ### Breaking change
9 + - Changed the tool field type to MapSet
10 +
11 + ### Improved
12 + - Better system function concatenation with tools description
13 + - Moved List.reverse from messages to Jason.Encoder implementation.
14 +
1 15 ## 0.4.0 - 2024-03-13
2 16
3 17 ### Improved
  @@ -10,6 +10,7 @@ This unofficial Elixir wrapper provides a convenient way to interact with the [A
10 10
11 11 ## To-dos
12 12 - Add streaming handling
13 + - Allow for Tool do define different models.
13 14
14 15 ## Installation
15 16 The package can be installed by adding `anthropic` to your list of dependencies in `mix.exs`:
  @@ -23,7 +24,7 @@ end
23 24 ```
24 25
25 26 ## Configuration
26 - Add or create a config file to provide the `api_key` as in the example below.
27 + Add or create a config file to provide the `api_key` as in the example below. Or pass an `:api_key` as option on `Anthropic.new(api_key: "key")`
27 28
28 29 ```elixir
29 30 # config/config.exs
  @@ -103,7 +104,7 @@ end
103 104
104 105 {:ok, response, request} =
105 106 Anthropic.new()
106 - |> Anthropic.register_tool(MyTool)
107 + |> Anthropic.register_tool(MyApp.WeatherTool)
107 108 |> Anthropic.add_user_message("Use the MyApp.WeatherTool to perform a task.")
108 109 |> Anthropic.request_next_message()
109 110 |> Anthropic.process_invocations()
  @@ -1,7 +1,7 @@
1 1 {<<"links">>,
2 2 [{<<"GitHub">>,<<"https://github.com/tubedude/anthropic-community">>}]}.
3 3 {<<"name">>,<<"anthropic_community">>}.
4 - {<<"version">>,<<"0.4.0">>}.
4 + {<<"version">>,<<"0.4.1">>}.
5 5 {<<"description">>,<<"Unofficial Anthropic API wrapper.">>}.
6 6 {<<"elixir">>,<<"~> 1.15">>}.
7 7 {<<"app">>,<<"anthropic">>}.
  @@ -36,6 +36,7 @@
36 36 <<"lib/anthropic/messages/request.ex">>,
37 37 <<"lib/anthropic/messages/content">>,
38 38 <<"lib/anthropic/messages/content/image.ex">>,<<"lib/anthropic/config.ex">>,
39 - <<"lib/anthropic/application.ex">>,<<"lib/anthropic.ex">>,
40 - <<".formatter.exs">>,<<"mix.exs">>,<<"README.md">>,<<"CHANGELOG.md">>]}.
39 + <<"lib/anthropic/http_client.ex">>,<<"lib/anthropic/application.ex">>,
40 + <<"lib/anthropic.ex">>,<<".formatter.exs">>,<<"mix.exs">>,<<"README.md">>,
41 + <<"CHANGELOG.md">>]}.
41 42 {<<"build_tools">>,[<<"mix">>]}.
  @@ -64,15 +64,16 @@ defmodule Anthropic do
64 64 By attaching to these events, you can monitor the performance and health of your Anthropic API integration, track usage metrics, and handle exceptions as needed.
65 65 """
66 66
67 + require Logger
67 68 alias Anthropic.Messages.Response
68 69 alias Anthropic.Messages.Request
69 70 alias Anthropic.Messages.Content.Image
70 71 alias Anthropic.Config
71 72
72 73 @type role :: :user | :assistant
73 - @type message :: %{role: role, content: any()}
74 + @type message :: %{role: role(), content: any()}
74 75
75 - @spec new(Anthropic.Config.config_options() | nil) :: Anthropic.Messages.Request.t()
76 + @spec new(keyword()) :: struct()
76 77 @doc """
77 78 Initializes a new `Anthropic.Messages.Request` struct with the given options, merging them with the default configuration `Anthropic.Config`.
78 79
  @@ -85,9 +86,17 @@ defmodule Anthropic do
85 86 - A new `Anthropic.Messages.Request` struct populated with the merged configuration options.
86 87 """
87 88 def new(opts \\ []) do
88 - Config.opts()
89 - |> Map.merge(Enum.into(opts, %{}))
90 - |> then(fn map -> struct(Anthropic.Config, map) end)
89 + config =
90 + case Keyword.get(opts, :config, Config.create(opts)) do
91 + %Anthropic.Config{} = value ->
92 + value
93 +
94 + error ->
95 + raise ArgumentError,
96 + "Config must be a valid %Anthropic.Config{}. Got: #{inspect(error)}"
97 + end
98 +
99 + Keyword.merge(opts, config: config)
91 100 |> Request.create()
92 101 end
93 102
  @@ -120,11 +129,6 @@ defmodule Anthropic do
120 129 "System message must be type String, got #{inspect(message, limit: 50, structs: false, width: 80)}"
121 130 )
122 131
123 - @spec add_message(
124 - Anthropic.Messages.Request.t(),
125 - role(),
126 - String.t() | [String.t()] | Request.content_object() | [Request.content_object()]
127 - ) :: Anthropic.Messages.Request.t()
128 132 @doc """
129 133 Adds a message to the request with a specified role.
130 134
  @@ -161,6 +165,7 @@ defmodule Anthropic do
161 165 %{type: "image", source: %{data: "base64_encoded_data", type: "base64", media_type: "image/png"}}
162 166 ]
163 167 request = Anthropic.add_message(request, :user, content_objects)
168 + ```
164 169 """
165 170 def add_message(%Request{messages: messages} = request, role, content) when is_list(content) do
166 171 content_objects =
  @@ -172,9 +177,7 @@ defmodule Anthropic do
172 177
173 178 updated_messages =
174 179 messages
175 - |> Enum.reverse()
176 - |> then(fn list -> [%{role: role, content: content_objects} | list] end)
177 - |> Enum.reverse()
180 + |> List.insert_at(0, %{role: role, content: content_objects})
178 181
179 182 %{request | messages: updated_messages}
180 183 end
  @@ -206,13 +209,6 @@ defmodule Anthropic do
206 209 add_message(%Request{} = request, :user, message)
207 210 end
208 211
209 - @spec add_assistant_message(
210 - Anthropic.Messages.Request.t(),
211 - Anthropic.Messages.Request.message()
212 - | list(Anthropic.Messages.Request.message())
213 - | binary()
214 - | list(binary())
215 - ) :: Anthropic.Messages.Request.t()
216 212 @doc """
217 213 Adds a assistant message to the request.
218 214
  @@ -320,20 +316,31 @@ defmodule Anthropic do
320 316 """
321 317 @doc since: "0.4.0"
322 318 def register_tool(%Request{} = request, tool_module) when is_atom(tool_module) do
323 - case {Code.ensure_loaded?(tool_module), Enum.member?(request.tools, tool_module)} do
324 - {true, true} ->
325 - request
326 -
327 - {true, false} ->
328 - %{request | tools: [tool_module | request.tools]}
329 -
330 - {false, _} ->
319 + if Code.ensure_loaded?(tool_module) do
320 + %{request | tools: MapSet.put(request.tools, tool_module)}
321 + else
331 322 raise ArgumentError,
332 323 "Module #{tool_module} is not loaded. Please use module full name (MyApp.AnthropicTool)"
333 324 end
325 +
326 + # case {Code.ensure_loaded?(tool_module), Enum.member?(request.tools, tool_module)} do
327 + # {true, true} ->
328 + # request
329 +
330 + # {true, false} ->
331 + # %{request | tools: [tool_module | request.tools]}
332 +
333 + # {false, _} ->
334 + # raise ArgumentError,
335 + # "Module #{tool_module} is not loaded. Please use module full name (MyApp.AnthropicTool)"
336 + # end
334 337 end
335 338
336 - @spec request_next_message(Anthropic.Messages.Request.t(), any()) :: any()
339 + def remove_tool(%Request{} = request, tool_module) do
340 + %{request | tools: MapSet.delete(request.tools, tool_module)}
341 + end
342 +
343 + @spec request_next_message(Anthropic.Messages.Request.t(), keyword() | nil) :: any()
337 344 @doc """
338 345 Sends the current request to the Anthropic API and awaits the next message in the conversation.
339 346
  @@ -365,7 +372,6 @@ defmodule Anthropic do
365 372 |> wrap_to_telemetry()
366 373 end
367 374
368 - # Prepares the response from the API for successful requests, updating the request with the assistant's message.
369 375 defp prepare_response({:ok, response}, request) do
370 376 updated_request =
371 377 request
  @@ -374,7 +380,6 @@ defmodule Anthropic do
374 380 {:ok, response, updated_request}
375 381 end
376 382
377 - # Handles error responses from the API, passing through the error and the original request for further handling.
378 383 defp prepare_response({:error, response}, request) do
379 384 {:error, response, request}
380 385 end
  @@ -6,8 +6,7 @@ defmodule Anthropic.Application do
6 6 @doc false
7 7 def start(_type, _args) do
8 8 children = [
9 - Anthropic.Config,
10 - {Finch, name: Anthropic.HTTPClient}
9 + {Finch, name: Anthropic.HTTPClient.Engine}
11 10 ]
12 11
13 12 opts = [strategy: :one_for_one, name: Anthropic.Supervisor]
Loading more files…