Class: Kernai::Providers::Anthropic

Inherits:
Kernai::Provider show all
Defined in:
lib/kernai/providers/anthropic.rb

Overview

Kernai::Provider adapter for the Anthropic Messages API.

Supports streaming, multimodal image input (when the model declares ‘:vision`), and extended thinking via `GenerationOptions#thinking`. Vendor-specific fields inside `GenerationOptions#extra` are ignored silently.

Zero runtime dependencies beyond the Ruby stdlib.

Examples:

provider = Kernai::Providers::Anthropic.new(api_key: ENV["ANTHROPIC_API_KEY"])
agent = Kernai::Agent.new(
  instructions: "You are a helpful assistant.",
  provider: provider,
  model: Kernai::Models::CLAUDE_SONNET_4,
  generation: { thinking: { budget: 10_000 }, max_tokens: 12_000 }
)

Constant Summary collapse

DEFAULT_API_URL =
'https://api.anthropic.com/v1/messages'
DEFAULT_API_VERSION =
'2023-06-01'
DEFAULT_MAX_TOKENS =
4096

Instance Method Summary collapse

Methods inherited from Kernai::Provider

#encode, #fallback_for

Constructor Details

#initialize(api_key: ENV.fetch('ANTHROPIC_API_KEY', nil), api_url: DEFAULT_API_URL, api_version: DEFAULT_API_VERSION) ⇒ Anthropic

Returns a new instance of Anthropic.



31
32
33
34
35
36
37
38
# File 'lib/kernai/providers/anthropic.rb', line 31

def initialize(api_key: ENV.fetch('ANTHROPIC_API_KEY', nil),
               api_url: DEFAULT_API_URL,
               api_version: DEFAULT_API_VERSION)
  super()
  @api_key = api_key
  @api_url = api_url
  @api_version = api_version
end

Instance Method Details

#call(messages:, model:, generation: nil, &block) ⇒ Object



40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# File 'lib/kernai/providers/anthropic.rb', line 40

def call(messages:, model:, generation: nil, &block)
  uri = URI(@api_url)
  system_msg, chat_messages = extract_system(messages, model)
  payload = build_payload(chat_messages, model,
                          system: system_msg, stream: block_given?, generation: generation)

  started = Process.clock_gettime(Process::CLOCK_MONOTONIC)
  response = http_post(uri, payload)

  unless response.is_a?(Net::HTTPSuccess)
    raise Kernai::ProviderError, "Anthropic API error #{response.code}: #{response.body}"
  end

  content, usage = if block
                     parse_stream(response.body, &block)
                   else
                     parse_response(response.body)
                   end

  Kernai::LlmResponse.new(
    content: content,
    latency_ms: elapsed_ms(started),
    prompt_tokens: usage[:input_tokens],
    completion_tokens: usage[:output_tokens]
  )
end

#encode_part(part, model:) ⇒ Object

Strings are always wrapped as ‘text, text: …` so the content array is homogeneous (Anthropic rejects mixed String/Hash arrays). Images — only when the model declares `:vision`. Any other media kind falls back to the base class placeholder, which is itself a String and will re-enter this method to be wrapped as a text block.



73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# File 'lib/kernai/providers/anthropic.rb', line 73

def encode_part(part, model:)
  return { 'type' => 'text', 'text' => part } if part.is_a?(String)
  return nil unless part.is_a?(Kernai::Media) && part.kind == :image && model.supports?(:vision)

  case part.source
  when :url
    { 'type' => 'image', 'source' => { 'type' => 'url', 'url' => part.data } }
  when :path, :bytes
    {
      'type' => 'image',
      'source' => {
        'type' => 'base64',
        'media_type' => part.mime_type,
        'data' => part.to_base64
      }
    }
  end
end