Class: Aigen::Google::HttpClient

Inherits:
Object
  • Object
show all
Defined in:
lib/aigen/google/http_client.rb

Constant Summary collapse

BASE_URL =
"https://generativelanguage.googleapis.com/v1beta"

Instance Method Summary collapse

Constructor Details

#initialize(api_key:, timeout: 30, retry_count: 3) ⇒ HttpClient

Returns a new instance of HttpClient.



11
12
13
14
15
# File 'lib/aigen/google/http_client.rb', line 11

def initialize(api_key:, timeout: 30, retry_count: 3)
  @api_key = api_key
  @timeout = timeout
  @retry_count = retry_count
end

Instance Method Details

#post(path, payload, attempt: 1) ⇒ Object



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
# File 'lib/aigen/google/http_client.rb', line 17

def post(path, payload, attempt: 1)
  response = connection.post(path) do |req|
    req.headers["Content-Type"] = "application/json"
    req.headers["x-goog-api-key"] = @api_key
    req.body = payload.to_json
  end

  handle_response(response, path, payload, attempt)
rescue Faraday::Error => e
  # Handle timeout-like errors (including WebMock's .to_timeout)
  is_timeout = e.is_a?(Faraday::TimeoutError) ||
    e.is_a?(::Timeout::Error) ||
    e.message.include?("execution expired") ||
    e.message.include?("timed out")

  if is_timeout
    if attempt >= @retry_count
      raise TimeoutError, "Request timed out after #{@retry_count} retries"
    end
    backoff_seconds = 2**(attempt - 1) # 1s, 2s, 4s
    sleep backoff_seconds
    post(path, payload, attempt: attempt + 1)
  else
    # Network connection errors
    raise ServerError.new("Network error: #{e.message}", status_code: nil)
  end
end

#post_stream(path, payload) {|chunk| ... } ⇒ nil

Makes a streaming POST request to the Gemini API. Processes chunked responses and yields each parsed chunk to the provided block.

Examples:

Stream generated content chunks

http_client.post_stream("models/gemini-pro:streamGenerateContent", payload) do |chunk|
  text = chunk["candidates"][0]["content"]["parts"][0]["text"]
  print text
end

Parameters:

  • the API endpoint path

  • the request payload (will be JSON encoded)

Yield Parameters:

  • chunk (Hash) —

    parsed JSON chunk from the streaming response

Returns:

  • always returns nil when block is given

Raises:

  • if no block is provided

  • if API key is invalid (401/403)

  • if request is malformed (400/404)

  • if rate limit is exceeded (429)

  • if server error occurs (500+) or network error



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
98
99
# File 'lib/aigen/google/http_client.rb', line 65

def post_stream(path, payload, &block)
  raise ArgumentError, "block required for streaming" unless block_given?

  buffer = ""

  response = connection.post(path) do |req|
    req.headers["Content-Type"] = "application/json"
    req.headers["x-goog-api-key"] = @api_key
    req.body = payload.to_json

    req.options.on_data = proc do |chunk, _total_bytes|
      buffer += chunk

      # Process complete lines (newline-delimited JSON)
      while (newline_index = buffer.index("\n"))
        line = buffer.slice!(0, newline_index + 1).strip
        next if line.empty?

        begin
          parsed_chunk = JSON.parse(line)
          block.call(parsed_chunk)
        rescue JSON::ParserError => e
          raise ServerError.new("Invalid JSON in stream chunk: #{e.message}", status_code: nil)
        end
      end
    end
  end

  # Check for non-200 status codes
  handle_stream_response_status(response)

  nil
rescue Faraday::Error => e
  raise ServerError.new("Network error during streaming: #{e.message}", status_code: nil)
end