Class: ActiveAgent::Provider::Anthropic

Inherits:
Base
  • Object
show all
Defined in:
lib/active_agent/providers/anthropic.rb

Instance Attribute Summary

Attributes inherited from Base

#api_key, #model

Instance Method Summary collapse

Methods inherited from Base

#initialize

Constructor Details

This class inherits a constructor from ActiveAgent::Provider::Base

Instance Method Details

#chat(messages, tools: [], &block) ⇒ Object



8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
# File 'lib/active_agent/providers/anthropic.rb', line 8

def chat(messages, tools: [], &block)
  api_model = model || "claude-3-5-sonnet-latest"
  url = "https://api.anthropic.com/v1/messages"

  system_message = messages.find { |m| m[:role].to_s == "system" }
  history_messages = messages.reject { |m| m[:role].to_s == "system" }

  body = {
    model: api_model,
    max_tokens: 4096,
    messages: format_messages(history_messages)
  }

  if system_message && !system_message[:content].to_s.empty?
    body[:system] = system_message[:content]
  end

  if tools.any?
    body[:tools] = format_tools(tools)
  end

  headers = {
    "Content-Type" => "application/json",
    "x-api-key" => api_key,
    "anthropic-version" => "2023-06-01"
  }

  if block_given?
    body[:stream] = true
    text_accumulator = ""
    tool_calls_accumulator = {}

    post_request(url, headers, body) do |line|
      next unless line.start_with?("data:")
      json_str = line.sub("data:", "").strip
      next if json_str.empty?

      begin
        event_data = JSON.parse(json_str)
        case event_data["type"]
        when "content_block_start"
          index = event_data["index"]
          block_info = event_data["content_block"]
          if block_info["type"] == "tool_use"
            tool_calls_accumulator[index] = {
              id: block_info["id"],
              name: block_info["name"],
              partial_json: ""
            }
          end
        when "content_block_delta"
          index = event_data["index"]
          delta = event_data["delta"]
          
          if delta["type"] == "text_delta"
            text_accumulator += delta["text"]
            yield(delta["text"])
          elsif delta["type"] == "input_json_delta"
            tool_calls_accumulator[index][:partial_json] += delta["partial_json"] if tool_calls_accumulator[index]
          end
        end
      rescue JSON::ParserError
        # Ignore json parse errors for incomplete lines
      end
    end

    tool_calls = tool_calls_accumulator.values.map do |tc|
      args = {}
      begin
        args = JSON.parse(tc[:partial_json], symbolize_names: true) unless tc[:partial_json].empty?
      rescue JSON::ParserError
        ActiveAgent.logger.warn("Anthropic could not parse streaming tool input JSON: #{tc[:partial_json]}")
      end

      {
        id: tc[:id],
        name: tc[:name],
        args: args
      }
    end

    result = { role: "assistant" }
    result[:content] = text_accumulator unless text_accumulator.empty?
    result[:tool_calls] = tool_calls if tool_calls.any?
    result
  else
    response_json = post_request(url, headers, body)
    parse_response(response_json)
  end
end

#format_tools(tools) ⇒ Object



99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
# File 'lib/active_agent/providers/anthropic.rb', line 99

def format_tools(tools)
  tools.map do |tool|
    properties = {}
    tool.parameters.each do |name, info|
      properties[name] = {
        type: Tool.map_type(info[:type], uppercase: false),
        description: info[:description]
      }
    end

    {
      name: tool.name,
      description: tool.description,
      input_schema: {
        type: "object",
        properties: properties,
        required: tool.required_parameters
      }
    }
  end
end