Module: Ask::LLM::HTTP
- Defined in:
- lib/ask/llm/http.rb
Overview
Shared HTTP infrastructure for all providers.
Class Method Summary collapse
-
.connection(base_url, headers: {}, request: {}) ⇒ Object
Build a Faraday connection for a provider.
-
.detect_rate_limit_type(body) ⇒ Symbol?
Detect rate limit type from error body.
-
.extract_error_message(body, status) ⇒ Object
Extract a human-readable error message from various provider error formats.
-
.extract_retry_after(headers) ⇒ Integer?
Extract retry_after from response headers.
-
.map_error(status, body, provider:, headers: nil) ⇒ Ask::Error
Map an HTTP exception or error response to the appropriate Ask::Error.
Class Method Details
.connection(base_url, headers: {}, request: {}) ⇒ Object
Build a Faraday connection for a provider.
8 9 10 11 12 13 14 |
# File 'lib/ask/llm/http.rb', line 8 def self.connection(base_url, headers: {}, request: {}) Faraday.new(url: base_url, headers: headers, request: request) do |f| f.request :json f.response :json, content_type: /\bjson$/ f.adapter Faraday.default_adapter end end |
.detect_rate_limit_type(body) ⇒ Symbol?
Detect rate limit type from error body.
90 91 92 93 94 95 96 97 98 99 100 |
# File 'lib/ask/llm/http.rb', line 90 def self.detect_rate_limit_type(body) return nil unless body.respond_to?(:dig) msg = body.dig("error", "message") || body.dig("error", "code") || "" msg_lower = msg.to_s.downcase return Ask::RateLimitType::BUDGET if msg_lower.include?("budget") || msg_lower.include?("quota") return Ask::RateLimitType::TOKENS if msg_lower.include?("token") || msg_lower.include?("tpm") return Ask::RateLimitType::CONCURRENT if msg_lower.include?("concurrent") || msg_lower.include?("parallel") Ask::RateLimitType::REQUESTS end |
.extract_error_message(body, status) ⇒ Object
Extract a human-readable error message from various provider error formats.
57 58 59 60 61 62 63 64 65 66 67 68 69 |
# File 'lib/ask/llm/http.rb', line 57 def self.(body, status) return nil unless body if body.respond_to?(:dig) body.dig("error", "message") || body.dig("error", "msg") || body.dig("error", "error") || body["message"] || body.to_s else body.to_s end end |
.extract_retry_after(headers) ⇒ Integer?
Extract retry_after from response headers. Providers send Retry-After as seconds (integer) or HTTP-date.
75 76 77 78 79 80 81 82 83 84 85 |
# File 'lib/ask/llm/http.rb', line 75 def self.extract_retry_after(headers) return nil unless headers.is_a?(Hash) raw = headers["retry-after"] || headers["Retry-After"] || headers["retry_after"] return nil unless raw int = Integer(raw) rescue nil return int if int Time.parse(raw) - Time.now rescue nil end |
.map_error(status, body, provider:, headers: nil) ⇒ Ask::Error
Map an HTTP exception or error response to the appropriate Ask::Error.
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 |
# File 'lib/ask/llm/http.rb', line 23 def self.map_error(status, body, provider:, headers: nil) body = JSON.parse(body) rescue body if body.is_a?(String) = (body, status) || "HTTP #{status} from #{provider}" # Check for context length exceeded regardless of status code err_code = body.respond_to?(:dig) ? body.dig("error", "code") : nil if err_code == "context_length_exceeded" return Ask::ContextLengthExceeded.new("#{provider}: #{}") end case status when 400 Ask::ProviderError.new(, status_code: status, response_body: body&.to_json) when 401, 403 Ask::Unauthorized.new("#{provider}: #{}") when 429 retry_after = extract_retry_after(headers) rate_limit_type = detect_rate_limit_type(body) Ask::RateLimitError.new( "#{provider}: #{}", category: Ask::RateLimitCategory::VENDOR, rate_limit_type: rate_limit_type, retry_after: retry_after ) when 500 Ask::ServerError.new("#{provider}: #{}") when 503 Ask::ServiceUnavailable.new("#{provider}: #{}") else Ask::ProviderError.new("#{provider}: #{}", status_code: status, response_body: body&.to_json) end end |