ask-llm-providers 0.8.0 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3cd9e34fd4befbdd393f4b4496b0d0e5dde254dc2ef10c3d86bb7043f81406de
4
- data.tar.gz: 6de8ecfb85b0aa79f5623a0968ea8a787dc8c64ebce9da6d5d49122ef82e037c
3
+ metadata.gz: 47102c012ea2700606f18c21510b875399a323d5a52ee91eca0b55c176b59468
4
+ data.tar.gz: 9e1e42e298a7e4d4ec40c3d9a3a631fbb9605e48816a439621ac4b4129cd0abe
5
5
  SHA512:
6
- metadata.gz: 10878f289f4fc79c814e839554adf05f7931e5a4eaa100cc5b584a20ee0bc16fece12c65472af23872abb30885e4e1cbdfc05310baffce4cd4f1dc4be60a622d
7
- data.tar.gz: c2fbe34457dcd128b2601baf756257d6147761505150b700d988d130e580017b9e6839ad9b5ba0ea6f91ec398ec1e09ae2bf3c363c2b6508a2d44a9c16368235
6
+ metadata.gz: effa863afea39f4dc924a1df6d88dce2ebb8639f6d607230df47233a54ef4e124cd998d0efe1a7e63ef5c2bcb6e7dadc7814804f5b9bb35bd82e0ee3c661de00
7
+ data.tar.gz: 7081eb62a0db815a56cfdaa04212cf4cc96fbd88fd17c4b8f5109535186d30bfa7a2e20d642cfe8617162eb2313a0229f18ad7e4d968cb29e9b8bfec590352e9
data/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ ## [0.8.1] — 2026-07-17
2
+
3
+ ### Added
4
+
5
+ - **Rich error categories in HTTP mapper** — `Ask::LLM::HTTP.map_error` now sets `category`, `rate_limit_type`, and `retry_after` on `RateLimitError` instances. Rate limit type is detected from error message keywords (token, budget, concurrent, requests). `retry_after` is extracted from response headers.
6
+
1
7
  ## [0.8.0] — 2026-07-17
2
8
 
3
9
  ### Added
data/lib/ask/llm/http.rb CHANGED
@@ -14,7 +14,13 @@ module Ask
14
14
  end
15
15
 
16
16
  # Map an HTTP exception or error response to the appropriate Ask::Error.
17
- def self.map_error(status, body, provider:)
17
+ #
18
+ # @param status [Integer] HTTP status code
19
+ # @param body [Hash, String, nil] response body
20
+ # @param provider [String] provider name for error messages
21
+ # @param headers [Hash, nil] response headers (for retry-after etc.)
22
+ # @return [Ask::Error]
23
+ def self.map_error(status, body, provider:, headers: nil)
18
24
  body = JSON.parse(body) rescue body if body.is_a?(String)
19
25
  message = extract_error_message(body, status) || "HTTP #{status} from #{provider}"
20
26
 
@@ -25,12 +31,25 @@ module Ask
25
31
  end
26
32
 
27
33
  case status
28
- when 400 then Ask::ProviderError.new(message, status_code: status, response_body: body&.to_json)
29
- when 401, 403 then Ask::Unauthorized.new("#{provider}: #{message}")
30
- when 429 then Ask::RateLimitError.new("#{provider}: #{message}")
31
- when 500 then Ask::ServerError.new("#{provider}: #{message}")
32
- when 503 then Ask::ServiceUnavailable.new("#{provider}: #{message}")
33
- else Ask::ProviderError.new("#{provider}: #{message}", status_code: status, response_body: body&.to_json)
34
+ when 400
35
+ Ask::ProviderError.new(message, status_code: status, response_body: body&.to_json)
36
+ when 401, 403
37
+ Ask::Unauthorized.new("#{provider}: #{message}")
38
+ when 429
39
+ retry_after = extract_retry_after(headers)
40
+ rate_limit_type = detect_rate_limit_type(body)
41
+ Ask::RateLimitError.new(
42
+ "#{provider}: #{message}",
43
+ category: Ask::RateLimitCategory::VENDOR,
44
+ rate_limit_type: rate_limit_type,
45
+ retry_after: retry_after
46
+ )
47
+ when 500
48
+ Ask::ServerError.new("#{provider}: #{message}")
49
+ when 503
50
+ Ask::ServiceUnavailable.new("#{provider}: #{message}")
51
+ else
52
+ Ask::ProviderError.new("#{provider}: #{message}", status_code: status, response_body: body&.to_json)
34
53
  end
35
54
  end
36
55
 
@@ -48,6 +67,37 @@ module Ask
48
67
  body.to_s
49
68
  end
50
69
  end
70
+
71
+ # Extract retry_after from response headers.
72
+ # Providers send Retry-After as seconds (integer) or HTTP-date.
73
+ # @param headers [Hash, nil]
74
+ # @return [Integer, nil]
75
+ def self.extract_retry_after(headers)
76
+ return nil unless headers.is_a?(Hash)
77
+
78
+ raw = headers["retry-after"] || headers["Retry-After"] || headers["retry_after"]
79
+ return nil unless raw
80
+
81
+ int = Integer(raw) rescue nil
82
+ return int if int
83
+
84
+ Time.parse(raw) - Time.now rescue nil
85
+ end
86
+
87
+ # Detect rate limit type from error body.
88
+ # @param body [Hash, nil]
89
+ # @return [Symbol, nil]
90
+ def self.detect_rate_limit_type(body)
91
+ return nil unless body.respond_to?(:dig)
92
+
93
+ msg = body.dig("error", "message") || body.dig("error", "code") || ""
94
+ msg_lower = msg.to_s.downcase
95
+
96
+ return Ask::RateLimitType::BUDGET if msg_lower.include?("budget") || msg_lower.include?("quota")
97
+ return Ask::RateLimitType::TOKENS if msg_lower.include?("token") || msg_lower.include?("tpm")
98
+ return Ask::RateLimitType::CONCURRENT if msg_lower.include?("concurrent") || msg_lower.include?("parallel")
99
+ Ask::RateLimitType::REQUESTS
100
+ end
51
101
  end
52
102
  end
53
103
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Ask
4
4
  module LLM
5
- VERSION = "0.8.0"
5
+ VERSION = "0.8.1"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ask-llm-providers
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.0
4
+ version: 0.8.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kaka Ruto