Class: ActiveAgent::Provider::OpenAI

Inherits:
Base
  • Object
show all
Defined in:
lib/active_agent/providers/openai.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
# File 'lib/active_agent/providers/openai.rb', line 8

def chat(messages, tools: [], &block)
  api_model = model || "gpt-4o-mini"
  url = "https://api.openai.com/v1/chat/completions"

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

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

  headers = {
    "Content-Type" => "application/json",
    "Authorization" => "Bearer #{api_key}"
  }

  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 == "[DONE]"
      next if json_str.empty?

      begin
        chunk = JSON.parse(json_str)
        choice = chunk.dig("choices", 0)
        next unless choice

        delta = choice["delta"]
        next unless delta

        if delta["content"]
          text_accumulator += delta["content"]
          yield(delta["content"])
        end

        if delta["tool_calls"]
          delta["tool_calls"].each do |tc|
            index = tc["index"]
            tool_calls_accumulator[index] ||= { id: "", name: "", arguments_str: "" }
            
            tool_calls_accumulator[index][:id] = tc["id"] if tc["id"]
            tool_calls_accumulator[index][:name] = tc.dig("function", "name") if tc.dig("function", "name")
            
            if tc.dig("function", "arguments")
              tool_calls_accumulator[index][:arguments_str] += tc.dig("function", "arguments")
            end
          end
        end
      rescue JSON::ParserError
        # Ignore json parse errors for incomplete lines
      end
    end

    # Format accumulated tool calls back to standard structure
    tool_calls = tool_calls_accumulator.values.map do |tc|
      args = {}
      begin
        args = JSON.parse(tc[:arguments_str], symbolize_names: true) unless tc[:arguments_str].empty?
      rescue JSON::ParserError
        ActiveAgent.logger.warn("OpenAI could not parse streaming arguments JSON: #{tc[:arguments_str]}")
      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



94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/active_agent/providers/openai.rb', line 94

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

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