Module: LlmCostTracker::Integrations::Openai
- Extended by:
- Base
- Defined in:
- lib/llm_cost_tracker/integrations/openai.rb,
lib/llm_cost_tracker/integrations/openai/patches.rb,
lib/llm_cost_tracker/integrations/openai/batch_capture.rb
Defined Under Namespace
Modules: BatchCapture, BatchesPatch, ChatCompletionsPatch, PatchBuilder, ResponsesPatch
Constant Summary collapse
- EmbeddingsPatch =
PatchBuilder.build(record_method: :record_response, methods: %i[create])
- ImagesPatch =
PatchBuilder.build(record_method: :record_image, methods: %i[generate edit create_variation])
- TranscriptionsPatch =
PatchBuilder.build(record_method: :record_transcription, methods: %i[create])
- TranslationsPatch =
PatchBuilder.build(record_method: :record_transcription, methods: %i[create])
- SpeechPatch =
PatchBuilder.build(record_method: :record_speech, methods: %i[create])
- ModerationsPatch =
PatchBuilder.build(record_method: :record_moderation, methods: %i[create])
- StreamingImagesPatch =
PatchBuilder.build_stream(methods: %i[generate_stream_raw edit_stream_raw])
- StreamingTranscriptionsPatch =
PatchBuilder.build_stream(methods: %i[create_streaming])
Class Method Summary collapse
- .auxiliary_patch_targets ⇒ Object
- .client_host_for(resource) ⇒ Object
- .patch_targets ⇒ Object
- .provider_for_host(host) ⇒ Object
- .record_image(response, request:, latency_ms:, host: nil) ⇒ Object
- .record_moderation(response, request:, latency_ms:, host: nil) ⇒ Object
- .record_passthrough(model:, response:, latency_ms:, host: nil, service_line_items: [], **token_attributes) ⇒ Object
- .record_response(response, request:, latency_ms:, host: nil) ⇒ Object
- .record_speech(_response, request:, latency_ms:, host: nil) ⇒ Object
- .record_transcription(response, request:, latency_ms:, host: nil) ⇒ Object
- .speech_line_items(request) ⇒ Object
- .stream_collector(request, host: nil) ⇒ Object
- .stream_pricing_mode(request, host: nil) ⇒ Object
- .stream_seam(resource) ⇒ Object
- .transcription_token_attributes(usage) ⇒ Object
- .usage_hash_from(response) ⇒ Object
Methods included from Base
active?, enforce_budget!, gem_version, install, integration_name, minimum_version, patch_target, patch_targets, provider, record_safely, request_params, status, stream_collector, stream_pricing_mode, track_stream, wrap_blocking, wrap_stream
Class Method Details
.auxiliary_patch_targets ⇒ Object
67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
# File 'lib/llm_cost_tracker/integrations/openai.rb', line 67 def auxiliary_patch_targets [ patch_target("OpenAI::Resources::Embeddings", with: EmbeddingsPatch, optional: true), patch_target("OpenAI::Resources::Images", with: ImagesPatch, optional: true), patch_target("OpenAI::Resources::Images", with: StreamingImagesPatch, optional: true, skip_when_methods_missing: true), patch_target("OpenAI::Resources::Audio::Transcriptions", with: TranscriptionsPatch, optional: true), patch_target("OpenAI::Resources::Audio::Transcriptions", with: StreamingTranscriptionsPatch, optional: true, skip_when_methods_missing: true), patch_target("OpenAI::Resources::Audio::Translations", with: TranslationsPatch, optional: true), patch_target("OpenAI::Resources::Audio::Speech", with: SpeechPatch, optional: true), patch_target("OpenAI::Resources::Moderations", with: ModerationsPatch, optional: true), patch_target("OpenAI::Resources::Batches", with: BatchesPatch, optional: true) ] end |
.client_host_for(resource) ⇒ Object
46 47 48 49 50 51 52 53 |
# File 'lib/llm_cost_tracker/integrations/openai.rb', line 46 def client_host_for(resource) client = resource.instance_variable_get(:@client) return nil unless client URI.parse(client.base_url.to_s).host rescue URI::InvalidURIError nil end |
.patch_targets ⇒ Object
59 60 61 62 63 64 65 |
# File 'lib/llm_cost_tracker/integrations/openai.rb', line 59 def patch_targets [ patch_target("OpenAI::Resources::Responses", with: ResponsesPatch), patch_target("OpenAI::Resources::Chat::Completions", with: ChatCompletionsPatch), *auxiliary_patch_targets ] end |
.provider_for_host(host) ⇒ Object
55 56 57 |
# File 'lib/llm_cost_tracker/integrations/openai.rb', line 55 def provider_for_host(host) LlmCostTracker::Providers::Azure::Hosts.openai?(host) ? "azure_openai" : "openai" end |
.record_image(response, request:, latency_ms:, host: nil) ⇒ Object
110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 |
# File 'lib/llm_cost_tracker/integrations/openai.rb', line 110 def record_image(response, request:, latency_ms:, host: nil) usage = usage_hash_from(response) || {} raw_input = usage[:input_tokens].to_i image_input = LlmCostTracker::Providers::Openai::UsageExtractor.image_input_tokens(usage) cache_read = LlmCostTracker::Providers::Openai::UsageExtractor.cache_read_input_tokens(usage) image_output, text_output = LlmCostTracker::Providers::Openai::UsageExtractor.split_output( output_tokens: usage[:output_tokens].to_i, image_output_details: LlmCostTracker::Providers::Openai::UsageExtractor.image_output_tokens(usage), text_output_details: LlmCostTracker::Providers::Openai::UsageExtractor.text_output_tokens(usage), audio_output: 0, default_to_image: true ) record_passthrough( model: request[:model], response: response, latency_ms: latency_ms, host: host, input_tokens: [raw_input - image_input - cache_read, 0].max, image_input_tokens: image_input, output_tokens: text_output, image_output_tokens: image_output, cache_read_input_tokens: cache_read ) end |
.record_moderation(response, request:, latency_ms:, host: nil) ⇒ Object
185 186 187 188 189 190 191 192 193 194 |
# File 'lib/llm_cost_tracker/integrations/openai.rb', line 185 def record_moderation(response, request:, latency_ms:, host: nil) record_passthrough( model: response.model || request[:model], response: response, latency_ms: latency_ms, host: host, input_tokens: 0, output_tokens: 0 ) end |
.record_passthrough(model:, response:, latency_ms:, host: nil, service_line_items: [], **token_attributes) ⇒ Object
196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 |
# File 'lib/llm_cost_tracker/integrations/openai.rb', line 196 def record_passthrough(model:, response:, latency_ms:, host: nil, service_line_items: [], **token_attributes) return unless active? record_safely do LlmCostTracker::Tracker.record( event: Event.build( provider: provider_for_host(host), model: model, token_usage: Usage::TokenUsage.build(**token_attributes), usage_source: LlmCostTracker::Usage::Source::SDK_RESPONSE, provider_response_id: response&.try(:id), service_line_items: service_line_items ), latency_ms: latency_ms ) end end |
.record_response(response, request:, latency_ms:, host: nil) ⇒ Object
87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 |
# File 'lib/llm_cost_tracker/integrations/openai.rb', line 87 def record_response(response, request:, latency_ms:, host: nil) return unless active? record_safely do normalized = LlmCostTracker::Capture::SdkPayload.normalize(response) usage = normalized["usage"] if usage input_tokens = usage["input_tokens"] || usage["prompt_tokens"] output_tokens = usage["output_tokens"] || usage["completion_tokens"] next if input_tokens.nil? && output_tokens.nil? end event = LlmCostTracker::Providers::Openai::ResponseParser.event_from_response( response: normalized, request: request, provider: provider_for_host(host), host: host, usage_source: LlmCostTracker::Usage::Source::SDK_RESPONSE ) LlmCostTracker::Tracker.record(event: event, latency_ms: latency_ms) if event end end |
.record_speech(_response, request:, latency_ms:, host: nil) ⇒ Object
159 160 161 162 163 164 165 166 167 168 169 |
# File 'lib/llm_cost_tracker/integrations/openai.rb', line 159 def record_speech(_response, request:, latency_ms:, host: nil) record_passthrough( model: request[:model], response: nil, latency_ms: latency_ms, host: host, input_tokens: 0, output_tokens: 0, service_line_items: speech_line_items(request) ) end |
.record_transcription(response, request:, latency_ms:, host: nil) ⇒ Object
135 136 137 138 139 140 141 142 143 144 145 |
# File 'lib/llm_cost_tracker/integrations/openai.rb', line 135 def record_transcription(response, request:, latency_ms:, host: nil) usage = usage_hash_from(response) record_passthrough( model: request[:model], response: response, latency_ms: latency_ms, host: host, service_line_items: LlmCostTracker::Providers::Openai::ServiceCharges.transcription_line_items(usage), **transcription_token_attributes(usage) ) end |
.speech_line_items(request) ⇒ Object
171 172 173 174 175 176 177 178 179 180 181 182 183 |
# File 'lib/llm_cost_tracker/integrations/openai.rb', line 171 def speech_line_items(request) input = request[:input] return [] unless input.is_a?(String) return [] unless LlmCostTracker::Providers::Openai::ModelFamilies.character_billed_tts?(request[:model]) [LlmCostTracker::Charges::LineItem.build( dimension_key: "text_to_speech_character", quantity: input.length, cost_status: LlmCostTracker::Charges::CostStatus::UNKNOWN, pricing_basis: "provider_usage", provider_field: "request.input" )] end |
.stream_collector(request, host: nil) ⇒ Object
29 30 31 32 33 34 35 36 |
# File 'lib/llm_cost_tracker/integrations/openai.rb', line 29 def stream_collector(request, host: nil) LlmCostTracker::Capture::StreamCollector.new( provider: provider_for_host(host), model: request[:model], pricing_mode: stream_pricing_mode(request, host: host), request: request ) end |
.stream_pricing_mode(request, host: nil) ⇒ Object
21 22 23 24 25 26 27 |
# File 'lib/llm_cost_tracker/integrations/openai.rb', line 21 def stream_pricing_mode(request, host: nil) LlmCostTracker::Providers::Openai::ResponseParser.combined_pricing_mode( host: host, model: (request || {})[:model], service_tier: (request || {})[:service_tier] ) end |
.stream_seam(resource) ⇒ Object
38 39 40 41 42 43 44 |
# File 'lib/llm_cost_tracker/integrations/openai.rb', line 38 def stream_seam(resource) host = client_host_for(resource) { provider: provider_for_host(host), collector: ->(request) { stream_collector(request, host: host) } } end |
.transcription_token_attributes(usage) ⇒ Object
147 148 149 150 151 152 153 154 155 156 157 |
# File 'lib/llm_cost_tracker/integrations/openai.rb', line 147 def transcription_token_attributes(usage) return { input_tokens: 0, output_tokens: 0 } unless usage && usage[:type].to_s == "tokens" raw_input = usage[:input_tokens].to_i audio_input = LlmCostTracker::Providers::Openai::UsageExtractor.audio_input_tokens(usage) { input_tokens: [raw_input - audio_input, 0].max, audio_input_tokens: audio_input, output_tokens: usage[:output_tokens].to_i } end |
.usage_hash_from(response) ⇒ Object
214 215 216 |
# File 'lib/llm_cost_tracker/integrations/openai.rb', line 214 def usage_hash_from(response) response.try(:usage)&.deep_to_h end |