ask-llm-providers 0.1.5 → 0.1.8

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: 98e73dfa923583db079237386caab024766c6613b202623b73952d3233ddf3ae
4
- data.tar.gz: 0aeef8d5eface2a50ffa4365c1d8e53fb47de3d0be54eb88c4ba53dea6fecfff
3
+ metadata.gz: e0a952555ce477b6a2e1ad523112f779a25cd526e0f255a8b6bac78dca33e1c8
4
+ data.tar.gz: 1b90e7fe032a0295fff1a9284045b08bb1a3f82d6c3559b9ecf7f47b7a0d2d0c
5
5
  SHA512:
6
- metadata.gz: 94dd687fc071961fe2495e61788861d676470b42e7352ff5f2d9c53455fe090d9d93c1b122a7b1d769f347d81b7059b44fffb18235fa7c66910b36b193b9f890
7
- data.tar.gz: 88d1dbcb9f4fb92d8a447352c725c6e27e89bbc29f46f7d83e96856c9288ec18bcbda236b04e6e7eb85df901187e3f5fcef56c464f5b101b3a12a53534dc2f20
6
+ metadata.gz: 57eea3bd9cb97969545688494f37be34cc029e94ea7ee0224efd8041b7534e8b6fab82af2c35f276ef9a7b7f8a64a84edff94972b92508554d37b4dafe556f90
7
+ data.tar.gz: 0fc0ba47aa5437fd38cc0734ca84b15736072d925dea337d3a8f12f2226379c0fa6824e9648829ca5c67860460039457402ec87740690844c89eed97d6c6a3eb
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Ask
4
4
  module LLM
5
- VERSION = "0.1.5"
5
+ VERSION = "0.1.8"
6
6
  end
7
7
  end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ask
4
+ module Providers
5
+ # DeepSeek API — an OpenAI-compatible provider at api.deepseek.com.
6
+ # Supports DeepSeek V2, V3, V4 Chat, R1 reasoning, and all DeepSeek models
7
+ # via the OpenAI-compatible endpoint.
8
+ #
9
+ # Configuration via environment:
10
+ # DEEPSEEK_API_KEY — required, your DeepSeek API key
11
+ # DEEPSEEK_API_BASE — optional, base URL (default: https://api.deepseek.com)
12
+ class DeepSeek < OpenAI
13
+ def api_base
14
+ @config.base_url || ENV["DEEPSEEK_API_BASE"] || "https://api.deepseek.com"
15
+ end
16
+
17
+ def headers
18
+ key = @config.api_key || ENV["DEEPSEEK_API_KEY"]
19
+ h = { "Content-Type" => "application/json" }
20
+ h["Authorization"] = "Bearer #{key}" if key
21
+ h
22
+ end
23
+
24
+ class << self
25
+ def slug; "deepseek"; end
26
+ def configuration_options; %i[api_key base_url]; end
27
+ def configuration_requirements; %i[api_key]; end
28
+ def configured?(config)
29
+ key = config.respond_to?(:api_key) ? config.api_key : nil
30
+ key ||= ENV["DEEPSEEK_API_KEY"]
31
+ key.to_s.length > 0
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -71,16 +71,23 @@ module Ask
71
71
  config.reject { |k, _| known.include?(k.to_sym) }
72
72
  end
73
73
 
74
- # Restore provider-specific keys after normalize_config strips standard ones.
74
+ # Resolve provider config from passed options, env vars, and ask-auth chain.
75
75
  def normalize_config(config)
76
76
  return config if !config.is_a?(Hash)
77
77
 
78
+ slug = self.class.slug
79
+ env_key = ENV["#{slug.upcase}_API_KEY"]
80
+ auth_key = Ask::Auth.resolve(:"#{slug}_api_key") rescue nil
81
+
78
82
  merged = {
79
- api_key: config[:api_key] || config["api_key"] || config[:openai_api_key],
80
- base_url: config[:base_url] || config["base_url"],
83
+ api_key: config[:api_key] || config["api_key"] ||
84
+ config[:"#{slug}_api_key"] || config[:openai_api_key] ||
85
+ env_key || auth_key,
86
+ base_url: config[:base_url] || config["base_url"] ||
87
+ ENV["#{slug.upcase}_API_BASE"],
81
88
  organization_id: config[:organization_id] || config["organization_id"],
82
89
  project_id: config[:project_id] || config["project_id"]
83
- }.merge(@provider_keys)
90
+ }.merge(config.reject { |k, _| %i[api_key base_url organization_id project_id openai_api_key].include?(k.to_sym) })
84
91
 
85
92
  Ask::LLM::Config.new(merged)
86
93
  end
@@ -174,23 +181,3 @@ module Ask
174
181
  end
175
182
  end
176
183
  end
177
-
178
- # When the OpenAI provider is subclassed (e.g. OpenCode), normalize_config
179
- # should also check for env vars matching the subclass slug.
180
- def normalize_config(config)
181
- return config if !config.is_a?(Hash)
182
-
183
- slug = self.class.slug
184
- env_key = ENV["#{slug.upcase}_API_KEY"]
185
- env_base = ENV["#{slug.upcase}_API_BASE"]
186
-
187
- merged = {
188
- api_key: config[:api_key] || config["api_key"] || config[:"#{slug}_api_key"] || config[:"#{slug}_api_key"] || config[:openai_api_key] || env_key,
189
- base_url: config[:base_url] || config["base_url"] || env_base,
190
- organization_id: config[:organization_id] || config["organization_id"],
191
- project_id: config[:project_id] || config["project_id"]
192
- }.merge(config.reject { |k, _| %i[api_key base_url organization_id project_id openai_api_key].include?(k.to_sym) })
193
-
194
- # Also preserve original config for subclass-specific key access
195
- Ask::LLM::Config.new(merged)
196
- end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ask
4
+ module Providers
5
+ # OpenRouter API — an OpenAI-compatible aggregator at openrouter.ai.
6
+ # Provides access to many models through a single endpoint.
7
+ #
8
+ # Configuration via environment:
9
+ # OPENROUTER_API_KEY — required, your OpenRouter API key
10
+ # OPENROUTER_API_BASE — optional, base URL (default: https://openrouter.ai/api/v1)
11
+ class OpenRouter < OpenAI
12
+ def api_base
13
+ @config.base_url || ENV["OPENROUTER_API_BASE"] || "https://openrouter.ai/api/v1"
14
+ end
15
+
16
+ def headers
17
+ h = super
18
+ key = @config.api_key || ENV["OPENROUTER_API_KEY"]
19
+ h["Authorization"] = "Bearer #{key}" if key
20
+ h["HTTP-Referer"] = ENV["OPENROUTER_REFERER"] || "https://github.com/ask-rb"
21
+ h["X-Title"] = ENV["OPENROUTER_APP_TITLE"] || "ask-rb"
22
+ h
23
+ end
24
+
25
+ class << self
26
+ def slug; "openrouter"; end
27
+ def configuration_options; %i[api_key base_url]; end
28
+ def configuration_requirements; %i[api_key]; end
29
+ def configured?(config)
30
+ key = config.respond_to?(:api_key) ? config.api_key : nil
31
+ key ||= ENV["OPENROUTER_API_KEY"]
32
+ key.to_s.length > 0
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,164 @@
1
+ ---
2
+ name: providers.model_select
3
+ description: How to select the right LLM model for a task — balancing cost, capability, latency, and context window
4
+ ---
5
+
6
+ Use this skill when choosing an LLM model for a specific task. The ask-rb
7
+ ecosystem uses `Ask::ModelCatalog` to resolve models and check capabilities.
8
+
9
+ ## Step 1: Classify the Task
10
+
11
+ Determine what kind of task you're solving:
12
+
13
+ | Task Type | Examples | Key Requirements |
14
+ |-----------|----------|-----------------|
15
+ | **Simple chat** | Q&A, summarization, translation | Speed, low cost |
16
+ | **Code generation** | Write functions, review PRs | Strong coding, large context |
17
+ | **Reasoning/analysis** | Debugging, architecture, planning | Deep reasoning, structured output |
18
+ | **Structured extraction** | Parse logs, extract data | JSON mode, function calling |
19
+ | **Vision/multimodal** | Screenshot analysis, document OCR | Image input support |
20
+ | **Long document** | Analyze 100+ page docs | Large context window (200K+) |
21
+ | **Embeddings** | Semantic search, RAG | Embedding model, dimensions |
22
+
23
+ ## Step 2: Query the Model Catalog
24
+
25
+ Access available models through the catalog:
26
+
27
+ ```ruby
28
+ # List all models
29
+ Ask::ModelCatalog.all
30
+
31
+ # Filter by capability
32
+ Ask::ModelCatalog.chat_models
33
+ Ask::ModelCatalog.by_provider("openai")
34
+ Ask::ModelCatalog.by_family("gpt")
35
+ Ask::ModelCatalog.embedding_models
36
+ ```
37
+
38
+ Find a specific model by ID:
39
+
40
+ ```ruby
41
+ model = Ask::ModelCatalog.find("gpt-4o")
42
+ model.context_window # => 128000
43
+ model.max_output_tokens # => 16384
44
+ model.supports?(:function_calling) # => true
45
+ model.capabilities # => ["function_calling", "structured_output", "reasoning", "vision"]
46
+ model.modalities # => { input: ["text", "image"], output: ["text"] }
47
+ ```
48
+
49
+ If the catalog doesn't have the model you need, refresh:
50
+
51
+ ```ruby
52
+ Ask::ModelCatalog.refresh!
53
+ ```
54
+
55
+ ## Step 3: Evaluate Cost vs Capability
56
+
57
+ Use pricing data from the catalog:
58
+
59
+ ```ruby
60
+ model = Ask::ModelCatalog.find("gpt-4o")
61
+ pricing = model.pricing.dig(:text_tokens, :standard)
62
+ pricing[:input_per_million] # $ per 1M input tokens
63
+ pricing[:output_per_million] # $ per 1M output tokens
64
+ ```
65
+
66
+ **Cost comparison (approximate):**
67
+
68
+ | Tier | Models | Cost/M tokens (in) | Best For |
69
+ |------|--------|-------------------|----------|
70
+ | **Frontier** | GPT-4o, Claude 4 Sonnet, Gemini 2.5 Pro | $3-15 | Complex reasoning, code generation |
71
+ | **Fast/Cheap** | GPT-4o-mini, Claude 4 Haiku, Gemini 2.5 Flash | $0.15-1.00 | Simple chat, extraction, classification |
72
+ | **Reasoning** | o3, o4-mini, DeepSeek R1 | $2-10 | Deep analysis, math, multi-step tasks |
73
+ | **Specialized** | Embedding, image, audio models | Varies | Non-chat tasks |
74
+
75
+ ## Step 4: Match Capabilities to Task Requirements
76
+
77
+ Check if a model supports the features you need:
78
+
79
+ ```ruby
80
+ model.supports?(:function_calling) # For tool use
81
+ model.supports?(:structured_output) # For JSON mode
82
+ model.supports?(:vision) # For image analysis
83
+ model.supports?(:reasoning) # For complex reasoning
84
+ ```
85
+
86
+ **Capability requirements by task:**
87
+
88
+ | Need | Check | Fallback |
89
+ |------|-------|----------|
90
+ | Tool calling | `supports?(:function_calling)` | Use text instruction instead |
91
+ | JSON output | `supports?(:structured_output)` | Prompt-engineering |
92
+ | Image processing | `modalities[:input].include?("image")` | Describe image in text |
93
+ | Audio processing | `modalities[:input].include?("audio")` | Transcribe first |
94
+ | Deep reasoning | `supports?(:reasoning)` | Chain-of-thought prompting |
95
+
96
+ ## Step 5: Consider Context Window Requirements
97
+
98
+ Choose context window based on your input size:
99
+
100
+ ```ruby
101
+ model.context_window # total tokens the model can process
102
+ ```
103
+
104
+ **Guidelines:**
105
+ - **8K-16K** — Simple Q&A, short conversations
106
+ - **32K-64K** — Code review, medium documents, multi-turn conversations
107
+ - **100K-200K** — Large codebases, long documents, RAG with many chunks
108
+ - **1M-2M** — Gemini 2.5 Pro, Gemini 2.0 Flash for massive documents
109
+
110
+ Be aware that large context windows increase latency and cost even if you don't
111
+ use them all.
112
+
113
+ ## Step 6: Pick the Right Embedding Model
114
+
115
+ For RAG and semantic search:
116
+
117
+ ```ruby
118
+ Ask::ModelCatalog.embedding_models
119
+ ```
120
+
121
+ **Recommendations:**
122
+ - **General purpose**: `text-embedding-3-large` (256-3072 dims)
123
+ - **Best accuracy**: `text-embedding-3-large` with 3072 dimensions
124
+ - **Fast/Cheap**: `text-embedding-3-small` (512 dimensions)
125
+ - **Multilingual**: `text-embedding-3-small` (supports 100+ languages)
126
+
127
+ ## Decision Tree
128
+
129
+ ```
130
+ Task Type?
131
+ ├── Simple chat / extraction
132
+ │ └── Fast model (GPT-4o-mini, Claude 4 Haiku)
133
+ │ → Cheapest adequate model
134
+ ├── Code generation / review
135
+ │ └── Frontier model (GPT-4o, Claude 4 Sonnet)
136
+ │ → Needs function calling + max capability
137
+ ├── Deep reasoning / debugging
138
+ │ └── Reasoning model (o4-mini, DeepSeek R1, o3)
139
+ │ → Needs chain-of-thought + analysis
140
+ ├── Long document analysis
141
+ │ └── Large context (Gemini 2.5 Pro 1M, GPT-4o)
142
+ │ → Needs context window > input size
143
+ ├── Multimodal (image/video)
144
+ │ └── Vision-capable (GPT-4o, Claude 4 Sonnet, Gemini 2.5)
145
+ │ → Check modalities[:input] includes image
146
+ ├── Embeddings / RAG
147
+ │ └── text-embedding-3-large / small
148
+ │ → Not a chat model
149
+ └── Audio / Voice
150
+ └── GPT-4o-audio, Gemini Audio
151
+ → Check modalities[:output] includes audio
152
+ ```
153
+
154
+ ## Provider Selection
155
+
156
+ Consider provider reliability and features:
157
+
158
+ | Provider | Strengths | Weaknesses |
159
+ |----------|-----------|------------|
160
+ | **OpenAI** | Best tool calling, broad model range | Higher cost for frontier |
161
+ | **Anthropic** | Excellent code, long context | Slower for simple tasks |
162
+ | **Google Gemini** | Massive context (1M+), fast | Fewer integration tools |
163
+ | **DeepSeek** | Cheap reasoning, open weights | Limited ecosystem |
164
+ | **Ollama** | Local, free, private | Slow, no hosted offerings |
@@ -23,6 +23,8 @@ require_relative "ask/provider/cloudflare"
23
23
  require_relative "ask/provider/opencode"
24
24
  require_relative "ask/provider/opencode_go"
25
25
  require_relative "ask/provider/mimo"
26
+ require_relative "ask/provider/deepseek"
27
+ require_relative "ask/provider/openrouter"
26
28
 
27
29
  # Register providers with the Ask::Provider registry
28
30
  Ask::Provider.register(:openai, Ask::Providers::OpenAI)
@@ -35,6 +37,8 @@ Ask::Provider.register(:cloudflare, Ask::Providers::Cloudflare)
35
37
  Ask::Provider.register(:opencode, Ask::Providers::OpenCode)
36
38
  Ask::Provider.register(:opencode_go, Ask::Providers::OpenCodeGo)
37
39
  Ask::Provider.register(:mimo, Ask::Providers::Mimo)
40
+ Ask::Provider.register(:deepseek, Ask::Providers::DeepSeek)
41
+ Ask::Provider.register(:openrouter, Ask::Providers::OpenRouter)
38
42
 
39
43
  # Register known models for each provider in the catalog
40
44
  [
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.1.5
4
+ version: 0.1.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kaka Ruto
@@ -182,6 +182,7 @@ files:
182
182
  - lib/ask/provider/anthropic.rb
183
183
  - lib/ask/provider/bedrock.rb
184
184
  - lib/ask/provider/cloudflare.rb
185
+ - lib/ask/provider/deepseek.rb
185
186
  - lib/ask/provider/google.rb
186
187
  - lib/ask/provider/mimo.rb
187
188
  - lib/ask/provider/mistral.rb
@@ -189,6 +190,8 @@ files:
189
190
  - lib/ask/provider/openai.rb
190
191
  - lib/ask/provider/opencode.rb
191
192
  - lib/ask/provider/opencode_go.rb
193
+ - lib/ask/provider/openrouter.rb
194
+ - lib/ask/skills/providers.model_select/SKILL.md
192
195
  homepage: https://github.com/ask-rb/ask-llm-providers
193
196
  licenses:
194
197
  - MIT