Module: Payloop::Wrappers::Base

Defined in:
lib/payloop/wrappers/base.rb

Overview

Base functionality for all provider wrappers

Instance Method Summary collapse

Instance Method Details

#payloop_merge_streaming_chunk(accumulated, chunk) ⇒ Object



94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
# File 'lib/payloop/wrappers/base.rb', line 94

def payloop_merge_streaming_chunk(accumulated, chunk)
  chunk.each do |key, value|
    if accumulated.key?(key)
      case accumulated[key]
      when Hash
        payloop_merge_streaming_chunk(accumulated[key], value) if value.is_a?(Hash)
      when Array
        # Concatenate arrays (matches Python SDK behavior: data[key].extend(chunk_value))
        accumulated[key].concat(value) if value.is_a?(Array)
      else
        accumulated[key] = value
      end
    else
      accumulated[key] = value
    end
  end
  accumulated
end

#payloop_normalize_openai_chunk(chunk) ⇒ Object



113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
# File 'lib/payloop/wrappers/base.rb', line 113

def payloop_normalize_openai_chunk(chunk)
  # Normalize OpenAI streaming chunks to match Python SDK format
  # Ensure delta objects have all keys present with null values
  return chunk unless chunk.is_a?(Hash)

  if chunk["choices"].is_a?(Array)
    chunk["choices"].each do |choice|
      next unless choice.is_a?(Hash) && choice.key?("delta")

      delta = choice["delta"]
      next unless delta.is_a?(Hash)

      # Add missing keys with nil values to match Python SDK
      delta["role"] ||= nil unless delta.key?("role")
      delta["content"] ||= nil unless delta.key?("content")
      delta["refusal"] ||= nil unless delta.key?("refusal")
      delta["tool_calls"] ||= nil unless delta.key?("tool_calls")
      delta["function_call"] ||= nil unless delta.key?("function_call")
    end
  end

  chunk
end

#payloop_submit_analytics(method:, args:, kwargs:, response:, start_time:, end_time:, provider: nil, title: nil, version: nil, status: "succeeded", exception: nil) ⇒ Object



46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# File 'lib/payloop/wrappers/base.rb', line 46

def payloop_submit_analytics(method:, args:, kwargs:, response:, start_time:,
                             end_time:, provider: nil, title: nil, version: nil,
                             status: "succeeded", exception: nil)
  collector = instance_variable_get(:@payloop_collector)
  config = instance_variable_get(:@payloop_config)

  return unless collector && config

  payload = build_payload(
    query: extract_query(method, args, kwargs),
    response: extract_response(response),
    start_time: start_time,
    end_time: end_time,
    config: config,
    status: status,
    provider: provider,
    title: title,
    version: version,
    exception: exception
  )

  collector.submit_async(payload)
end

#payloop_submit_error_analytics(method:, args:, kwargs:, error:, start_time:, end_time:, provider: nil, title: nil, version: nil, response: nil) ⇒ Object

Submit a failed-call payload to the collector. ‘response:` is optional —when omitted, the default `{ error:, class: }` shape is used. Wrappers that preserve richer data on error (e.g. Groq’s streaming wrapper merging accumulated chunks with error info via build_error_response) pass it explicitly; the backend extractor reads whatever fields it can from the supplied hash and falls back gracefully on missing keys.



76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
# File 'lib/payloop/wrappers/base.rb', line 76

def payloop_submit_error_analytics(method:, args:, kwargs:, error:, start_time:,
                                   end_time:, provider: nil, title: nil, version: nil,
                                   response: nil)
  payloop_submit_analytics(
    method: method,
    args: args,
    kwargs: kwargs,
    response: response || { error: error.message, class: error.class.name },
    start_time: start_time,
    end_time: end_time,
    provider: provider,
    title: title,
    version: version,
    status: "failed",
    exception: error.message
  )
end

#payloop_wrap_method(method_name, _provider_name) ⇒ Object



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
# File 'lib/payloop/wrappers/base.rb', line 9

def payloop_wrap_method(method_name, _provider_name)
  return if method(method_name).source_location&.first&.include?("payloop")

  original_method = instance_method(method_name)

  define_method(method_name) do |*args, **kwargs, &block|
    start_time = Time.now

    # Call original method
    response = original_method.bind(self).call(*args, **kwargs, &block)

    # Submit analytics asynchronously
    payloop_submit_analytics(
      method: method_name,
      args: args,
      kwargs: kwargs,
      response: response,
      start_time: start_time,
      end_time: Time.now
    )

    response
  rescue StandardError => e
    # Submit error analytics
    payloop_submit_error_analytics(
      method: method_name,
      args: args,
      kwargs: kwargs,
      error: e,
      start_time: start_time,
      end_time: Time.now
    )

    raise e
  end
end