Current section
Files
Jump to
Current section
Files
lib/providers/anthropic.ex
defmodule HyperLLM.Provider.Anthropic do
@behaviour HyperLLM.Provider
import HyperLLM.Provider
@moduledoc """
Provider implementation for Anthropic.
https://docs.anthropic.com/en/api/messages
## Configuration
`api_key` - The API key for the Anthropic API.
`api_version` - The API version to use. Defaults to `2023-06-01`.
congfig :hyper_llm,
anthropic: [
api_key: "sk-...",
api_version: "2023-06-01"
]
"""
@models [
"claude-3-7-sonnet-latest",
"claude-3-7-sonnet-20250219",
"claude-3-5-sonnet-latest",
"claude-3-5-sonnet-20240620",
"claude-3-5-haiku-latest",
"claude-3-5-haiku-20241022",
"claude-3-opus-20240229",
"claude-3-sonnet-20240229",
"claude-3-haiku-20240307"
]
@doc """
See `HyperLLM.Chat.completion/3` for more information.
"""
@impl HyperLLM.Provider
@spec completion(HyperLLM.Provider.completion_params(), HyperLLM.Provider.completion_config()) ::
{:ok, binary()} | {:error, binary()}
def completion(params, config \\ []) do
if !Map.has_key?(params, :messages) do
raise ArgumentError, ":messages are required in params"
end
if !Map.has_key?(params, :model) do
raise ArgumentError, ":model is required in config"
end
if !Map.has_key?(params, :max_tokens) do
raise ArgumentError, ":max_tokens is required in params"
end
request("/messages",
method: :post,
receive_timeout: Keyword.get(config, :receive_timeout, 30_000),
json: to_anthropic_params(params)
)
|> to_openai_response()
end
@impl HyperLLM.Provider
@doc """
Check if a model is supported by the provider.
Supported Models:
#{Enum.map_join(@models, "\n", &"* #{&1}")}
"""
def model_supported?(model) when model in @models, do: true
def model_supported?(_), do: false
defp to_anthropic_params(params) do
params
|> rename_key(:stop, :stop_sequences, fn stop_sequences ->
if is_list(stop_sequences) do
stop_sequences
else
[stop_sequences]
end
end)
end
defp to_openai_response({request, %{status: 200, body: body}}) do
content = hd(body["content"])
usage = body["usage"]
%{
"id" => body["id"],
"created" => DateTime.utc_now() |> DateTime.to_unix(),
"model" => request.options.json.model,
"object" => "chat.completion",
"service_tier" => "default",
"system_fingerprint" => nil,
"choices" => [
%{
"index" => 0,
"message" => %{
"role" => "assistant",
"content" => content["text"]
},
"logprobs" => nil,
"finish_reason" => body["stop_reason"]
}
],
"usage" => %{
"prompt_tokens" => Map.get(usage, "input_tokens", 0),
"completion_tokens" => Map.get(usage, "output_tokens", 0),
"total_tokens" => Map.get(usage, "input_tokens", 0) + Map.get(usage, "output_tokens", 0)
}
}
end
defp to_openai_response({_, %{status: 400, body: body}}), do: {:error, body["error"]["message"]}
defp to_openai_response({_, %{status: 401}}), do: {:error, "Invalid API key"}
defp to_openai_response({_, %{status: 404}}), do: {:error, "Not found"}
defp to_openai_response({_, %{status: 500}}), do: {:error, "Server error"}
defp to_openai_response({_, %{status: 502}}), do: {:error, "Bad gateway"}
defp to_openai_response({_, %{status: 503}}), do: {:error, "Service unavailable"}
defp to_openai_response({_, %{status: 504}}), do: {:error, "Gateway timeout"}
defp to_openai_response({_, %{status: 505}}), do: {:error, "HTTP version not supported"}
defp request(url, opts) do
api_key = HyperLLM.config!(:anthropic, :api_key)
api_version = HyperLLM.config(:anthropic, :api_version, "2023-06-01")
req =
Req.new(
base_url: "https://api.anthropic.com/v1",
headers: [
{"x-api-key", api_key},
{"anthropic-version", api_version}
],
url: url
)
Req.run(req, opts)
end
end