ask-llm-providers 0.1.2 → 0.8.2

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.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +181 -0
  3. data/lib/ask/llm/aliases.json +11 -0
  4. data/lib/ask/llm/aliases.rb +62 -0
  5. data/lib/ask/llm/catalog.rb +221 -0
  6. data/lib/ask/llm/cost_calculator.rb +114 -0
  7. data/lib/ask/llm/http.rb +68 -12
  8. data/lib/ask/llm/models/anthropic.json +872 -0
  9. data/lib/ask/llm/models/bedrock.json +3769 -0
  10. data/lib/ask/llm/models/deepseek.json +340 -0
  11. data/lib/ask/llm/models/gemini.json +1211 -0
  12. data/lib/ask/llm/models/meta.json +299 -0
  13. data/lib/ask/llm/models/mistral.json +1303 -0
  14. data/lib/ask/llm/models/moonshot.json +198 -0
  15. data/lib/ask/llm/models/nvidia_nim.json +289 -0
  16. data/lib/ask/llm/models/openai.json +2521 -0
  17. data/lib/ask/llm/models/opencode.json +552 -0
  18. data/lib/ask/llm/models/perplexity.json +148 -0
  19. data/lib/ask/llm/models/vertex_ai.json +1347 -0
  20. data/lib/ask/llm/models/xai.json +326 -0
  21. data/lib/ask/llm/models_schema.json +74 -0
  22. data/lib/ask/llm/openai_compatible.rb +100 -0
  23. data/lib/ask/llm/provider_config.rb +65 -0
  24. data/lib/ask/llm/sources/models_dev.rb +154 -0
  25. data/lib/ask/llm/sources/openrouter.rb +176 -0
  26. data/lib/ask/llm/sse_buffer.rb +43 -0
  27. data/lib/ask/llm/version.rb +1 -1
  28. data/lib/ask/provider/anthropic.rb +106 -103
  29. data/lib/ask/provider/bedrock.rb +84 -52
  30. data/lib/ask/provider/cloudflare.rb +62 -40
  31. data/lib/ask/provider/google.rb +112 -74
  32. data/lib/ask/provider/mistral.rb +156 -6
  33. data/lib/ask/provider/ollama.rb +83 -29
  34. data/lib/ask/provider/openai.rb +141 -68
  35. data/lib/ask/provider/openai_compatible.rb +91 -0
  36. data/lib/ask/skills/providers.model_select/SKILL.md +164 -0
  37. data/lib/ask-llm-providers.rb +22 -18
  38. metadata +35 -7
  39. data/lib/ask/llm/models/openai.rb +0 -69
data/lib/ask/llm/http.rb CHANGED
@@ -14,21 +14,42 @@ 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)
24
+ body = JSON.parse(body) rescue body if body.is_a?(String)
18
25
  message = extract_error_message(body, status) || "HTTP #{status} from #{provider}"
19
26
 
20
27
  # Check for context length exceeded regardless of status code
21
- if body&.dig("error", "code") == "context_length_exceeded"
28
+ err_code = body.respond_to?(:dig) ? body.dig("error", "code") : nil
29
+ if err_code == "context_length_exceeded"
22
30
  return Ask::ContextLengthExceeded.new("#{provider}: #{message}")
23
31
  end
24
32
 
25
33
  case status
26
- when 400 then Ask::ProviderError.new(message, status_code: status, response_body: body&.to_json)
27
- when 401, 403 then Ask::Unauthorized.new("#{provider}: #{message}")
28
- when 429 then Ask::RateLimitError.new("#{provider}: #{message}")
29
- when 500 then Ask::ServerError.new("#{provider}: #{message}")
30
- when 503 then Ask::ServiceUnavailable.new("#{provider}: #{message}")
31
- 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)
32
53
  end
33
54
  end
34
55
 
@@ -36,11 +57,46 @@ module Ask
36
57
  def self.extract_error_message(body, status)
37
58
  return nil unless body
38
59
 
39
- body.dig("error", "message") ||
40
- body.dig("error", "msg") ||
41
- body.dig("error", "error") ||
42
- body["message"] ||
60
+ if body.respond_to?(:dig)
61
+ body.dig("error", "message") ||
62
+ body.dig("error", "msg") ||
63
+ body.dig("error", "error") ||
64
+ body["message"] ||
65
+ body.to_s
66
+ else
43
67
  body.to_s
68
+ end
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
44
100
  end
45
101
  end
46
102
  end