Module: LlmCostTracker::Parsers::OpenaiServiceCharges
- Included in:
- OpenaiUsage
- Defined in:
- lib/llm_cost_tracker/parsers/openai_service_charges.rb
Constant Summary collapse
- RESPONSE_OUTPUT_COMPONENTS =
{ "web_search_call" => :web_search_request, "file_search_call" => :file_search_call, "code_interpreter_call" => :container_session, "mcp_call" => :mcp_call }.freeze
- CHAT_COMPLETIONS_ANNOTATION_PROVIDER_FIELD =
"choices.message.annotations.url_citation"- CHAT_COMPLETIONS_SEARCH_MODEL_PROVIDER_FIELD =
"request.model"
Class Method Summary collapse
- .billable?(item) ⇒ Boolean
- .build_line_item(item, request: nil, model: nil) ⇒ Object
- .chat_completions_search_model?(model) ⇒ Boolean
- .chat_completions_search_provider_field(choices, model) ⇒ Object
- .chat_completions_used_web_search?(choices) ⇒ Boolean
- .chat_completions_web_search_items(response, model: nil) ⇒ Object
- .component_key_for(item, request:, model:) ⇒ Object
- .line_item_details(item) ⇒ Object
- .line_items_from_output(output_items, request: nil, model: nil) ⇒ Object
- .openai_stream_service_line_items(events, request: nil, model: nil) ⇒ Object
- .reasoning_model?(model) ⇒ Boolean
- .service_line_items_for(response, request: nil, model: nil) ⇒ Object
- .store_output_item(output_items, item) ⇒ Object
- .web_search_preview_used?(request) ⇒ Boolean
Class Method Details
.billable?(item) ⇒ Boolean
60 61 62 63 64 65 66 67 68 69 |
# File 'lib/llm_cost_tracker/parsers/openai_service_charges.rb', line 60 def billable?(item) return false unless item.is_a?(Hash) component = RESPONSE_OUTPUT_COMPONENTS[item["type"]] return false unless component return true unless component == :web_search_request action_type = item.dig("action", "type") action_type.nil? || action_type == "search" end |
.build_line_item(item, request: nil, model: nil) ⇒ Object
83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 |
# File 'lib/llm_cost_tracker/parsers/openai_service_charges.rb', line 83 def build_line_item(item, request: nil, model: nil) return nil unless item.is_a?(Hash) component_key = component_key_for(item, request: request, model: model) return nil unless component_key provider_item_id = if component_key == :container_session item["container_id"] || item["id"] else item["id"] end Billing::LineItem.build( component_key: component_key, quantity: 1, cost_status: Billing::CostStatus::UNKNOWN, pricing_basis: :provider_usage, provider_field: item["provider_field"] || "response.output.#{item['type']}", provider_item_id: provider_item_id, details: line_item_details(item) ) end |
.chat_completions_search_model?(model) ⇒ Boolean
123 124 125 126 127 128 |
# File 'lib/llm_cost_tracker/parsers/openai_service_charges.rb', line 123 def chat_completions_search_model?(model) return false unless model name = model.to_s.split("/", 2).last LlmCostTracker::Providers::Openai::ModelFamilies.chat_completions_search?(name) end |
.chat_completions_search_provider_field(choices, model) ⇒ Object
45 46 47 48 49 50 |
# File 'lib/llm_cost_tracker/parsers/openai_service_charges.rb', line 45 def chat_completions_search_provider_field(choices, model) return CHAT_COMPLETIONS_ANNOTATION_PROVIDER_FIELD if chat_completions_used_web_search?(choices) return CHAT_COMPLETIONS_SEARCH_MODEL_PROVIDER_FIELD if chat_completions_search_model?(model) nil end |
.chat_completions_used_web_search?(choices) ⇒ Boolean
52 53 54 55 56 57 58 |
# File 'lib/llm_cost_tracker/parsers/openai_service_charges.rb', line 52 def chat_completions_used_web_search?(choices) Array(choices).any? do |choice| Array(choice.dig("message", "annotations")).any? do |annotation| annotation.is_a?(Hash) && annotation["type"] == "url_citation" end end end |
.chat_completions_web_search_items(response, model: nil) ⇒ Object
35 36 37 38 39 40 41 42 43 |
# File 'lib/llm_cost_tracker/parsers/openai_service_charges.rb', line 35 def chat_completions_web_search_items(response, model: nil) return [] unless response["choices"] provider_field = chat_completions_search_provider_field(response["choices"], model) return [] unless provider_field [{ "type" => "web_search_call", "id" => response["id"], "action" => { "type" => "search" }, "provider_field" => provider_field }] end |
.component_key_for(item, request:, model:) ⇒ Object
105 106 107 108 109 110 111 |
# File 'lib/llm_cost_tracker/parsers/openai_service_charges.rb', line 105 def component_key_for(item, request:, model:) component = RESPONSE_OUTPUT_COMPONENTS[item["type"]] return component unless component == :web_search_request return component unless web_search_preview_used?(request) || chat_completions_search_model?(model) reasoning_model?(model) ? :web_search_preview_request_reasoning : :web_search_preview_request_non_reasoning end |
.line_item_details(item) ⇒ Object
137 138 139 140 141 142 143 |
# File 'lib/llm_cost_tracker/parsers/openai_service_charges.rb', line 137 def line_item_details(item) { "status" => item["status"], "action_type" => item.dig("action", "type"), "container_id" => item["container_id"] }.compact end |
.line_items_from_output(output_items, request: nil, model: nil) ⇒ Object
18 19 20 21 22 23 24 |
# File 'lib/llm_cost_tracker/parsers/openai_service_charges.rb', line 18 def line_items_from_output(output_items, request: nil, model: nil) deduped = {} Array(output_items).each { |item| store_output_item(deduped, item) } deduped.values .select { |item| billable?(item) } .filter_map { |item| build_line_item(item, request: request, model: model) } end |
.openai_stream_service_line_items(events, request: nil, model: nil) ⇒ Object
145 146 147 148 149 150 151 152 |
# File 'lib/llm_cost_tracker/parsers/openai_service_charges.rb', line 145 def openai_stream_service_line_items(events, request: nil, model: nil) output_items = [] each_event_data(events) do |data| output_items.concat(Array(data.dig("response", "output"))) output_items << data["item"] if data["item"] end line_items_from_output(output_items, request: request, model: model) end |
.reasoning_model?(model) ⇒ Boolean
130 131 132 133 134 135 |
# File 'lib/llm_cost_tracker/parsers/openai_service_charges.rb', line 130 def reasoning_model?(model) return false unless model name = model.to_s.split("/", 2).last LlmCostTracker::Providers::Openai::ModelFamilies.reasoning?(name) end |
.service_line_items_for(response, request: nil, model: nil) ⇒ Object
26 27 28 29 30 |
# File 'lib/llm_cost_tracker/parsers/openai_service_charges.rb', line 26 def service_line_items_for(response, request: nil, model: nil) output_items = Array(response["output"]) output_items += chat_completions_web_search_items(response, model: model) if output_items.empty? line_items_from_output(output_items, request: request, model: model) end |
.store_output_item(output_items, item) ⇒ Object
71 72 73 74 75 76 77 78 79 80 81 |
# File 'lib/llm_cost_tracker/parsers/openai_service_charges.rb', line 71 def store_output_item(output_items, item) return unless item.is_a?(Hash) && RESPONSE_OUTPUT_COMPONENTS.key?(item["type"]) component = RESPONSE_OUTPUT_COMPONENTS[item["type"]] key = if component == :container_session && item["container_id"] "#{component}:#{item['container_id']}" else item["id"] || "#{item['type']}:#{output_items.length}" end output_items[key] = item end |
.web_search_preview_used?(request) ⇒ Boolean
113 114 115 116 117 118 119 120 121 |
# File 'lib/llm_cost_tracker/parsers/openai_service_charges.rb', line 113 def web_search_preview_used?(request) tools = request && (request[:tools] || request["tools"]) return false unless tools.respond_to?(:each) tools.any? do |tool| type = tool.is_a?(Hash) ? (tool[:type] || tool["type"]) : tool type.to_s.include?("web_search_preview") end end |