ask-llm-providers 0.2.1 → 0.3.0
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 +4 -4
- data/CHANGELOG.md +92 -0
- data/lib/ask/llm/aliases.json +11 -0
- data/lib/ask/llm/aliases.rb +62 -0
- data/lib/ask/llm/catalog.rb +194 -0
- data/lib/ask/llm/models/anthropic.json +188 -0
- data/lib/ask/llm/models/bedrock.json +1 -0
- data/lib/ask/llm/models/cloudflare.json +1 -0
- data/lib/ask/llm/models/deepseek.json +85 -0
- data/lib/ask/llm/models/gemini.json +151 -0
- data/lib/ask/llm/models/mimo.json +22 -0
- data/lib/ask/llm/models/mistral.json +62 -0
- data/lib/ask/llm/models/ollama.json +136 -0
- data/lib/ask/llm/models/openai.json +294 -0
- data/lib/ask/llm/models/opencode.json +189 -0
- data/lib/ask/llm/models/opencode_go.json +149 -0
- data/lib/ask/llm/models/openrouter.json +115 -0
- data/lib/ask/llm/models_schema.json +74 -0
- data/lib/ask/llm/version.rb +1 -1
- data/lib/ask-llm-providers.rb +4 -17
- metadata +24 -5
- data/lib/ask/llm/models/openai.rb +0 -69
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 925a11549bdce89470327b583421ae71aa3b8aa4385efacb9baead5ee8aa7f27
|
|
4
|
+
data.tar.gz: 8ec764b1b392cf83ac70afb56eb78df2e118150f2e25951580dd1efac628a8af
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4bff20df39628b886645cb48d8eb812d6f5e32ae8f5de2ffc433bd577c7e5dd4bf64b429b0efeffefbee79f2ce7af3bdc48a5cabb60f63e0ea0612e61d352130
|
|
7
|
+
data.tar.gz: 93bef4f89008fbcefcee9362ef9ca35369230012777e9202683d781ebdf872901d796fb847f2cbd74cf86faaa17801233bcaa2731f6058ee72ef45f6d7dadc2c
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
## [0.3.0] — 2026-07-14
|
|
2
|
+
|
|
3
|
+
### Added
|
|
4
|
+
- **Model catalog system** — `Ask::LLM::Catalog` loads model definitions from per-provider JSON files (`lib/ask/llm/models/*.json`), user overrides (`~/.ask-llm-providers/models.json`), and provider API `list_models()` on explicit refresh.
|
|
5
|
+
- **Per-provider model JSONs** — 12 JSON files (openai, anthropic, gemini, deepseek, opencode, opencode_go, mimo, openrouter, ollama, mistral, bedrock, cloudflare) with id, name, provider, capabilities, context window, modalities, and pricing.
|
|
6
|
+
- **Model aliases** — `Ask::LLM::Aliases` resolves short names (e.g. `claude-sonnet-4` → `claude-sonnet-4-6`). Alias entries are automatically registered into `Ask::ModelCatalog` so `ModelCatalog.find` works with alias names.
|
|
7
|
+
- **User config support** — `~/.ask-llm-providers/models.json` overrides bundled model fields or adds custom models.
|
|
8
|
+
- **`opencode.json` includes `deepseek-v4-flash`** — matches the default model configuration.
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
- Removed hardcoded `Ask::LLM::Models::OPENAI_MODELS` constants — replaced with catalog-driven model loading.
|
|
12
|
+
- `Ask::LLM::Aliases.resolve` now aliases `deepseek-v4` → `deepseek-v4-flash`, `gpt-4o-latest` → `gpt-4o`, `gpt-4.1-latest` → `gpt-4.1`.
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
- Model entries now include `"provider"` field in JSON files (was missing from generated data).
|
|
16
|
+
- User config merges properly override bundled values (was keeping old values on conflict).
|
|
17
|
+
|
|
18
|
+
## [0.2.2] — 2026-06-25
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
- Extended per-provider tests (Anthropic 18t, Google 14t, DeepSeek 16t, Mistral, Ollama, Cloudflare, Bedrock). Fixed providers_test.rb syntax error. RuboCop, overcommit, gemspec test, SimpleCov, CI.
|
|
22
|
+
# Changelog
|
|
23
|
+
|
|
24
|
+
## [0.1.0] — 2026-06-09
|
|
25
|
+
|
|
26
|
+
Initial release of `ask-llm-providers`, all LLM providers for the ask-rb ecosystem.
|
|
27
|
+
|
|
28
|
+
### Added
|
|
29
|
+
|
|
30
|
+
- **OpenAI provider** — Chat Completions API with streaming, tool calls, vision, structured output, embeddings
|
|
31
|
+
- **Anthropic provider** — Messages API with thinking blocks, tool use, prompt caching
|
|
32
|
+
- **Google Gemini provider** — `generateContent` API with function calling, streaming, embeddings
|
|
33
|
+
- **Amazon Bedrock provider** — Converse API with tool configuration
|
|
34
|
+
- **Ollama provider** — Local LLM inference with chat and embeddings endpoints
|
|
35
|
+
- **Mistral AI provider** — OpenAI-compatible API with embeddings support
|
|
36
|
+
- **Cloudflare provider** — Workers AI direct endpoint and AI Gateway passthrough
|
|
37
|
+
- **Error mapping** — Provider-specific HTTP errors → `Ask::Error` types (rate limit, auth, context exceeded, etc.)
|
|
38
|
+
- **Provider registration** — All providers auto-registered with `Ask::Provider` on gem load
|
|
39
|
+
- **Capabilities introspection** — Each provider exposes supported capabilities
|
|
40
|
+
- **Shared HTTP infrastructure** — `Ask::LLM::HTTP` with Faraday connection builder and SSE streaming
|
|
41
|
+
- **Test suite** — 33 tests across all providers and error mapping
|
|
42
|
+
|
|
43
|
+
## [0.1.8] — 2026-06-18
|
|
44
|
+
|
|
45
|
+
### Added
|
|
46
|
+
|
|
47
|
+
- **OpenRouter provider** — `Ask::Providers::OpenRouter`, reads `OPENROUTER_API_KEY`,
|
|
48
|
+
sets `HTTP-Referer` and `X-Title` headers.
|
|
49
|
+
|
|
50
|
+
### Fixed
|
|
51
|
+
|
|
52
|
+
- **`normalize_config` ENV resolution** — Removed broken monkey-patch that defined
|
|
53
|
+
`normalize_config` on `Object` instead of within the `OpenAI` class. Now resolves
|
|
54
|
+
`api_key` from: explicit config → subclass-specific key → `ENV` var →
|
|
55
|
+
`Ask::Auth.resolve` chain. All OpenAI-compatible subclasses (DeepSeek, OpenCode,
|
|
56
|
+
OpenCodeGo, Mimo, OpenRouter) inherit the fix.
|
|
57
|
+
|
|
58
|
+
## [0.1.9] — 2026-06-18
|
|
59
|
+
|
|
60
|
+
### Fixed
|
|
61
|
+
|
|
62
|
+
- **`format_messages` with Hash tool_calls** — Tool calls can arrive as Hash (keyed
|
|
63
|
+
by call_id) from some chat implementations, or as Array. The method now detects
|
|
64
|
+
both formats with `tc.is_a?(Hash) ? tc.values : tc`. Also handles `OpenStruct`
|
|
65
|
+
tool call objects via `.respond_to?` checks instead of assuming Hash accessors.
|
|
66
|
+
|
|
67
|
+
## [0.2.0] — 2026-06-19
|
|
68
|
+
|
|
69
|
+
### Fixed
|
|
70
|
+
|
|
71
|
+
- **SSE buffering across all streaming providers** — TCP fragmentation in
|
|
72
|
+
Faraday's `on_data` callback caused silent data loss when SSE events were
|
|
73
|
+
split across packets. Added persistent `@_sse_buffer` with complete-event
|
|
74
|
+
extraction via `Ask::LLM::SSEBuffer` module. Affected providers:
|
|
75
|
+
OpenAI, Anthropic, Cloudflare, Google, Ollama. All OpenAI-compatible
|
|
76
|
+
subclasses inherit the fix.
|
|
77
|
+
|
|
78
|
+
### Added
|
|
79
|
+
|
|
80
|
+
- **`Ask::LLM::SSEBuffer`** — Shared module providing `init_sse_buffer` and
|
|
81
|
+
`each_sse_event(raw)` for SSE buffering across streaming callbacks.
|
|
82
|
+
- **SSE buffering tests** — 5 new tests covering fragmented data, event
|
|
83
|
+
boundaries, multiple events, and `[DONE]` sentinel.
|
|
84
|
+
|
|
85
|
+
## [0.1.10] — 2026-06-18
|
|
86
|
+
|
|
87
|
+
### Fixed
|
|
88
|
+
|
|
89
|
+
- **`chat_stream` nil response body** — Streaming requests consume the response body
|
|
90
|
+
via `on_data` callback, leaving `resp.body` as `nil`. `JSON.parse(nil)` raised
|
|
91
|
+
`TypeError: no implicit conversion of nil into String`. Now checks
|
|
92
|
+
`resp.body` before parsing.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"claude-sonnet-4": "claude-sonnet-4-6",
|
|
3
|
+
"claude-sonnet-4-5": "claude-sonnet-4-6",
|
|
4
|
+
"claude-opus-4": "claude-opus-4-7",
|
|
5
|
+
"claude-opus-4-5": "claude-opus-4-7",
|
|
6
|
+
"claude-haiku-4": "claude-haiku-4-5",
|
|
7
|
+
"gemini-3": "gemini-3-flash",
|
|
8
|
+
"deepseek-v4": "deepseek-v4-flash",
|
|
9
|
+
"gpt-4o-latest": "gpt-4o",
|
|
10
|
+
"gpt-4.1-latest": "gpt-4.1"
|
|
11
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
module Ask
|
|
6
|
+
module LLM
|
|
7
|
+
# Resolves model name aliases to canonical model IDs.
|
|
8
|
+
#
|
|
9
|
+
# Aliases are defined in aliases.json and allow users to refer
|
|
10
|
+
# to models by shorter or more familiar names. Resolution is
|
|
11
|
+
# provider-scoped — you can alias "claude-sonnet-4" to different
|
|
12
|
+
# canonical IDs depending on which provider serves it.
|
|
13
|
+
#
|
|
14
|
+
# Ask::LLM::Aliases.resolve("claude-sonnet-4")
|
|
15
|
+
# # => "claude-sonnet-4-6"
|
|
16
|
+
#
|
|
17
|
+
# Aliases are loaded lazily from the bundled JSON file.
|
|
18
|
+
module Aliases
|
|
19
|
+
ALIASES_PATH = File.expand_path("aliases.json", __dir__)
|
|
20
|
+
|
|
21
|
+
class << self
|
|
22
|
+
# Resolve an alias to a canonical model ID.
|
|
23
|
+
# Returns the input name unchanged if no alias is registered.
|
|
24
|
+
def resolve(name)
|
|
25
|
+
load_aliases unless @aliases
|
|
26
|
+
@aliases[name.to_s] || name.to_s
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Register a custom alias at runtime.
|
|
30
|
+
def register(short_name, canonical_id)
|
|
31
|
+
load_aliases unless @aliases
|
|
32
|
+
@aliases[short_name.to_s] = canonical_id.to_s
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Reload aliases from the bundled JSON file.
|
|
36
|
+
def reload!
|
|
37
|
+
@aliases = nil
|
|
38
|
+
load_aliases
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# All registered aliases (for introspection).
|
|
42
|
+
def all
|
|
43
|
+
load_aliases unless @aliases
|
|
44
|
+
@aliases.dup
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
def load_aliases
|
|
50
|
+
@aliases = {}
|
|
51
|
+
path = ALIASES_PATH
|
|
52
|
+
return unless File.exist?(path)
|
|
53
|
+
|
|
54
|
+
raw = JSON.parse(File.read(path))
|
|
55
|
+
raw.each { |k, v| @aliases[k.to_s] = v.to_s }
|
|
56
|
+
rescue JSON::ParserError
|
|
57
|
+
# Invalid aliases file — log and use empty map
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "fileutils"
|
|
5
|
+
|
|
6
|
+
module Ask
|
|
7
|
+
module LLM
|
|
8
|
+
# Orchestrates model catalog loading from multiple sources:
|
|
9
|
+
#
|
|
10
|
+
# 1. Bundled JSON files in lib/ask/llm/models/*.json (shipped with the gem)
|
|
11
|
+
# 2. ~/.ask-llm-providers/models.json (user-defined overrides)
|
|
12
|
+
# 3. Provider API list_models() calls (on explicit refresh!)
|
|
13
|
+
#
|
|
14
|
+
# Loaded models are registered into Ask::ModelCatalog for use
|
|
15
|
+
# by ask-agent, ask-mcp, and llm-proxy.
|
|
16
|
+
#
|
|
17
|
+
# Ask::LLM::Catalog.load! # load bundled + user config
|
|
18
|
+
# Ask::LLM::Catalog.refresh! # also fetch from provider APIs
|
|
19
|
+
#
|
|
20
|
+
class Catalog
|
|
21
|
+
class Error < StandardError; end
|
|
22
|
+
class LoadError < Error; end
|
|
23
|
+
|
|
24
|
+
USER_CONFIG_PATH = File.expand_path("~/.ask-llm-providers/models.json").freeze
|
|
25
|
+
|
|
26
|
+
class << self
|
|
27
|
+
# Load bundled model definitions and user overrides into Ask::ModelCatalog.
|
|
28
|
+
# Idempotent — subsequent calls clear and reload.
|
|
29
|
+
def load!
|
|
30
|
+
instance.clear
|
|
31
|
+
instance.load_bundled
|
|
32
|
+
instance.load_user_config
|
|
33
|
+
instance.register_all
|
|
34
|
+
true
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Like load! but also fetches model lists from configured providers'
|
|
38
|
+
# list_models() APIs. Unknown models are added with minimal metadata.
|
|
39
|
+
def refresh!
|
|
40
|
+
load!
|
|
41
|
+
instance.fetch_from_providers
|
|
42
|
+
instance.register_all
|
|
43
|
+
true
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
def symbolize_keys(hash)
|
|
49
|
+
hash.transform_keys { |k| k.respond_to?(:to_sym) ? k.to_sym : k }
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def instance
|
|
53
|
+
@instance ||= new
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def initialize
|
|
58
|
+
@entries = []
|
|
59
|
+
@model_keys = Set.new
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def clear
|
|
63
|
+
@entries.clear
|
|
64
|
+
@model_keys.clear
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Load bundled model JSONs from the gem's lib/ask/llm/models/ directory.
|
|
68
|
+
def load_bundled
|
|
69
|
+
pattern = File.expand_path("models/*.json", __dir__)
|
|
70
|
+
Dir[pattern].sort.each do |path|
|
|
71
|
+
raw = JSON.parse(File.read(path))
|
|
72
|
+
raw.each { |entry| add_entry(entry) }
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Load user-defined model overrides from ~/.ask-llm-providers/models.json.
|
|
77
|
+
# Silently skipped if the file doesn't exist.
|
|
78
|
+
def load_user_config
|
|
79
|
+
path = USER_CONFIG_PATH
|
|
80
|
+
return unless File.exist?(path)
|
|
81
|
+
|
|
82
|
+
raw = JSON.parse(File.read(path))
|
|
83
|
+
unless raw.is_a?(Array)
|
|
84
|
+
warn "Warning: #{path} should be a JSON array of model entries, got #{raw.class}"
|
|
85
|
+
return
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
raw.each { |entry| merge_or_add(entry) }
|
|
89
|
+
rescue JSON::ParserError => e
|
|
90
|
+
warn "Warning: Failed to parse #{path}: #{e.message}"
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Fetch model lists from all configured providers via their list_models() API.
|
|
94
|
+
# Adds unknown models with minimal metadata (no capability guessing).
|
|
95
|
+
def fetch_from_providers
|
|
96
|
+
Ask::Provider.providers.each do |slug, provider_class|
|
|
97
|
+
next unless provider_class.configured?(nil)
|
|
98
|
+
|
|
99
|
+
begin
|
|
100
|
+
provider = provider_class.new
|
|
101
|
+
models = provider.list_models
|
|
102
|
+
models.each do |m|
|
|
103
|
+
add_entry(m) unless @model_keys.include?([m[:id], slug.to_s])
|
|
104
|
+
end
|
|
105
|
+
rescue StandardError => e
|
|
106
|
+
warn "Warning: Failed to fetch models from #{slug}: #{e.message}"
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Register all accumulated entries into Ask::ModelCatalog.
|
|
112
|
+
# Also registers alias entries so models can be found by alias name.
|
|
113
|
+
def register_all
|
|
114
|
+
@entries.each do |entry|
|
|
115
|
+
info = build_model_info(entry)
|
|
116
|
+
Ask::ModelCatalog.instance.register(info)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
register_alias_entries
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
private
|
|
123
|
+
|
|
124
|
+
# For each alias (short_name → canonical_id), register a duplicate
|
|
125
|
+
# ModelInfo for every canonical entry whose id matches.
|
|
126
|
+
def register_alias_entries
|
|
127
|
+
Ask::LLM::Aliases.all.each do |short_name, canonical_id|
|
|
128
|
+
next if short_name == canonical_id
|
|
129
|
+
|
|
130
|
+
@entries.each do |entry|
|
|
131
|
+
next unless entry["id"] == canonical_id || entry[:id] == canonical_id
|
|
132
|
+
|
|
133
|
+
alias_entry = entry.merge("id" => short_name)
|
|
134
|
+
info = build_model_info(alias_entry)
|
|
135
|
+
Ask::ModelCatalog.instance.register(info)
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def symbolize_keys(hash)
|
|
141
|
+
hash.transform_keys { |k| k.respond_to?(:to_sym) ? k.to_sym : k }
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def add_entry(entry)
|
|
145
|
+
key = entry_key(entry)
|
|
146
|
+
return if @model_keys.include?(key)
|
|
147
|
+
|
|
148
|
+
@entries << entry
|
|
149
|
+
@model_keys << key
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def merge_or_add(entry)
|
|
153
|
+
key = entry_key(entry)
|
|
154
|
+
existing = @entries.find { |e| entry_key(e) == key }
|
|
155
|
+
|
|
156
|
+
if existing
|
|
157
|
+
existing.merge!(entry)
|
|
158
|
+
else
|
|
159
|
+
@entries << entry
|
|
160
|
+
@model_keys << key
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def entry_key(entry)
|
|
165
|
+
id = entry["id"] || entry[:id]
|
|
166
|
+
provider = entry["provider"] || entry[:provider]
|
|
167
|
+
[id, provider.to_s]
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def build_model_info(entry)
|
|
171
|
+
e = entry.transform_keys(&:to_sym)
|
|
172
|
+
modalities = e[:modalities]
|
|
173
|
+
modalities = symbolize_keys(modalities) if modalities
|
|
174
|
+
|
|
175
|
+
Ask::ModelInfo.new(
|
|
176
|
+
id: e[:id],
|
|
177
|
+
name: e[:name] || e[:id],
|
|
178
|
+
provider: e[:provider],
|
|
179
|
+
family: e[:family],
|
|
180
|
+
capabilities: Array(e[:capabilities]),
|
|
181
|
+
context_window: e[:context_window],
|
|
182
|
+
max_output_tokens: e[:max_output_tokens],
|
|
183
|
+
modalities: modalities || { input: %w[text], output: %w[text] },
|
|
184
|
+
pricing: e[:pricing] || {},
|
|
185
|
+
knowledge_cutoff: e[:knowledge_cutoff] ? Date.parse(e[:knowledge_cutoff].to_s) : nil,
|
|
186
|
+
created_at: e[:created_at] ? Date.parse(e[:created_at].to_s) : nil,
|
|
187
|
+
metadata: (e[:metadata] || {}).merge(source: e[:metadata]&.dig("source") || "bundled")
|
|
188
|
+
)
|
|
189
|
+
rescue Date::Error
|
|
190
|
+
Ask::ModelInfo.new(id: e[:id], provider: e[:provider])
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
end
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"id": "claude-sonnet-4-5",
|
|
4
|
+
"name": "Claude Sonnet 4.5",
|
|
5
|
+
"family": "claude_sonnet",
|
|
6
|
+
"context_window": 200000,
|
|
7
|
+
"max_output_tokens": 64000,
|
|
8
|
+
"capabilities": [
|
|
9
|
+
"function_calling",
|
|
10
|
+
"streaming",
|
|
11
|
+
"structured_output",
|
|
12
|
+
"vision",
|
|
13
|
+
"reasoning",
|
|
14
|
+
"prompt_caching",
|
|
15
|
+
"tool_choice",
|
|
16
|
+
"parallel_tool_calls"
|
|
17
|
+
],
|
|
18
|
+
"modalities": {
|
|
19
|
+
"input": [
|
|
20
|
+
"text",
|
|
21
|
+
"image",
|
|
22
|
+
"pdf"
|
|
23
|
+
],
|
|
24
|
+
"output": [
|
|
25
|
+
"text"
|
|
26
|
+
]
|
|
27
|
+
},
|
|
28
|
+
"provider": "anthropic"
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"id": "claude-sonnet-4",
|
|
32
|
+
"name": "Claude Sonnet 4",
|
|
33
|
+
"family": "claude_sonnet",
|
|
34
|
+
"context_window": 200000,
|
|
35
|
+
"max_output_tokens": 64000,
|
|
36
|
+
"capabilities": [
|
|
37
|
+
"function_calling",
|
|
38
|
+
"streaming",
|
|
39
|
+
"structured_output",
|
|
40
|
+
"vision",
|
|
41
|
+
"reasoning",
|
|
42
|
+
"prompt_caching",
|
|
43
|
+
"tool_choice",
|
|
44
|
+
"parallel_tool_calls"
|
|
45
|
+
],
|
|
46
|
+
"modalities": {
|
|
47
|
+
"input": [
|
|
48
|
+
"text",
|
|
49
|
+
"image",
|
|
50
|
+
"pdf"
|
|
51
|
+
],
|
|
52
|
+
"output": [
|
|
53
|
+
"text"
|
|
54
|
+
]
|
|
55
|
+
},
|
|
56
|
+
"provider": "anthropic"
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"id": "claude-4-opus",
|
|
60
|
+
"name": "Claude Opus 4",
|
|
61
|
+
"family": "claude_opus",
|
|
62
|
+
"context_window": 200000,
|
|
63
|
+
"max_output_tokens": 64000,
|
|
64
|
+
"capabilities": [
|
|
65
|
+
"function_calling",
|
|
66
|
+
"streaming",
|
|
67
|
+
"structured_output",
|
|
68
|
+
"vision",
|
|
69
|
+
"reasoning",
|
|
70
|
+
"prompt_caching",
|
|
71
|
+
"tool_choice",
|
|
72
|
+
"parallel_tool_calls"
|
|
73
|
+
],
|
|
74
|
+
"modalities": {
|
|
75
|
+
"input": [
|
|
76
|
+
"text",
|
|
77
|
+
"image",
|
|
78
|
+
"pdf"
|
|
79
|
+
],
|
|
80
|
+
"output": [
|
|
81
|
+
"text"
|
|
82
|
+
]
|
|
83
|
+
},
|
|
84
|
+
"provider": "anthropic"
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
"id": "claude-haiku-4-5",
|
|
88
|
+
"name": "Claude Haiku 4.5",
|
|
89
|
+
"family": "claude_haiku",
|
|
90
|
+
"context_window": 200000,
|
|
91
|
+
"max_output_tokens": 64000,
|
|
92
|
+
"capabilities": [
|
|
93
|
+
"function_calling",
|
|
94
|
+
"streaming",
|
|
95
|
+
"structured_output",
|
|
96
|
+
"vision",
|
|
97
|
+
"reasoning",
|
|
98
|
+
"prompt_caching",
|
|
99
|
+
"tool_choice",
|
|
100
|
+
"parallel_tool_calls"
|
|
101
|
+
],
|
|
102
|
+
"modalities": {
|
|
103
|
+
"input": [
|
|
104
|
+
"text",
|
|
105
|
+
"image",
|
|
106
|
+
"pdf"
|
|
107
|
+
],
|
|
108
|
+
"output": [
|
|
109
|
+
"text"
|
|
110
|
+
]
|
|
111
|
+
},
|
|
112
|
+
"provider": "anthropic"
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
"id": "claude-opus-4-5",
|
|
116
|
+
"name": "Claude Opus 4.5",
|
|
117
|
+
"family": "claude_opus",
|
|
118
|
+
"context_window": 200000,
|
|
119
|
+
"max_output_tokens": 64000,
|
|
120
|
+
"capabilities": [
|
|
121
|
+
"function_calling",
|
|
122
|
+
"streaming",
|
|
123
|
+
"structured_output",
|
|
124
|
+
"vision",
|
|
125
|
+
"reasoning",
|
|
126
|
+
"prompt_caching",
|
|
127
|
+
"tool_choice",
|
|
128
|
+
"parallel_tool_calls"
|
|
129
|
+
],
|
|
130
|
+
"modalities": {
|
|
131
|
+
"input": [
|
|
132
|
+
"text",
|
|
133
|
+
"image",
|
|
134
|
+
"pdf"
|
|
135
|
+
],
|
|
136
|
+
"output": [
|
|
137
|
+
"text"
|
|
138
|
+
]
|
|
139
|
+
},
|
|
140
|
+
"provider": "anthropic"
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
"id": "claude-3.5-sonnet",
|
|
144
|
+
"name": "Claude 3.5 Sonnet",
|
|
145
|
+
"family": "claude_sonnet",
|
|
146
|
+
"context_window": 200000,
|
|
147
|
+
"max_output_tokens": 8192,
|
|
148
|
+
"capabilities": [
|
|
149
|
+
"function_calling",
|
|
150
|
+
"streaming",
|
|
151
|
+
"vision",
|
|
152
|
+
"reasoning"
|
|
153
|
+
],
|
|
154
|
+
"modalities": {
|
|
155
|
+
"input": [
|
|
156
|
+
"text",
|
|
157
|
+
"image"
|
|
158
|
+
],
|
|
159
|
+
"output": [
|
|
160
|
+
"text"
|
|
161
|
+
]
|
|
162
|
+
},
|
|
163
|
+
"provider": "anthropic"
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
"id": "claude-3.5-haiku",
|
|
167
|
+
"name": "Claude 3.5 Haiku",
|
|
168
|
+
"family": "claude_haiku",
|
|
169
|
+
"context_window": 200000,
|
|
170
|
+
"max_output_tokens": 8192,
|
|
171
|
+
"capabilities": [
|
|
172
|
+
"function_calling",
|
|
173
|
+
"streaming",
|
|
174
|
+
"vision",
|
|
175
|
+
"reasoning"
|
|
176
|
+
],
|
|
177
|
+
"modalities": {
|
|
178
|
+
"input": [
|
|
179
|
+
"text",
|
|
180
|
+
"image"
|
|
181
|
+
],
|
|
182
|
+
"output": [
|
|
183
|
+
"text"
|
|
184
|
+
]
|
|
185
|
+
},
|
|
186
|
+
"provider": "anthropic"
|
|
187
|
+
}
|
|
188
|
+
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
[]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
[]
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"id": "deepseek-v4-flash",
|
|
4
|
+
"name": "DeepSeek V4 Flash",
|
|
5
|
+
"family": "deepseek_v4",
|
|
6
|
+
"context_window": 1000000,
|
|
7
|
+
"max_output_tokens": 384000,
|
|
8
|
+
"capabilities": [
|
|
9
|
+
"function_calling",
|
|
10
|
+
"streaming",
|
|
11
|
+
"reasoning"
|
|
12
|
+
],
|
|
13
|
+
"modalities": {
|
|
14
|
+
"input": [
|
|
15
|
+
"text"
|
|
16
|
+
],
|
|
17
|
+
"output": [
|
|
18
|
+
"text"
|
|
19
|
+
]
|
|
20
|
+
},
|
|
21
|
+
"provider": "deepseek"
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"id": "deepseek-v4-pro",
|
|
25
|
+
"name": "DeepSeek V4 Pro",
|
|
26
|
+
"family": "deepseek_v4",
|
|
27
|
+
"context_window": 1000000,
|
|
28
|
+
"max_output_tokens": 384000,
|
|
29
|
+
"capabilities": [
|
|
30
|
+
"function_calling",
|
|
31
|
+
"streaming",
|
|
32
|
+
"reasoning"
|
|
33
|
+
],
|
|
34
|
+
"modalities": {
|
|
35
|
+
"input": [
|
|
36
|
+
"text"
|
|
37
|
+
],
|
|
38
|
+
"output": [
|
|
39
|
+
"text"
|
|
40
|
+
]
|
|
41
|
+
},
|
|
42
|
+
"provider": "deepseek"
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"id": "deepseek-chat",
|
|
46
|
+
"name": "DeepSeek Chat",
|
|
47
|
+
"family": "deepseek_v3",
|
|
48
|
+
"context_window": 64000,
|
|
49
|
+
"max_output_tokens": 8192,
|
|
50
|
+
"capabilities": [
|
|
51
|
+
"function_calling",
|
|
52
|
+
"streaming"
|
|
53
|
+
],
|
|
54
|
+
"modalities": {
|
|
55
|
+
"input": [
|
|
56
|
+
"text"
|
|
57
|
+
],
|
|
58
|
+
"output": [
|
|
59
|
+
"text"
|
|
60
|
+
]
|
|
61
|
+
},
|
|
62
|
+
"provider": "deepseek"
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
"id": "deepseek-reasoner",
|
|
66
|
+
"name": "DeepSeek Reasoner",
|
|
67
|
+
"family": "deepseek_r1",
|
|
68
|
+
"context_window": 64000,
|
|
69
|
+
"max_output_tokens": 8192,
|
|
70
|
+
"capabilities": [
|
|
71
|
+
"function_calling",
|
|
72
|
+
"streaming",
|
|
73
|
+
"reasoning"
|
|
74
|
+
],
|
|
75
|
+
"modalities": {
|
|
76
|
+
"input": [
|
|
77
|
+
"text"
|
|
78
|
+
],
|
|
79
|
+
"output": [
|
|
80
|
+
"text"
|
|
81
|
+
]
|
|
82
|
+
},
|
|
83
|
+
"provider": "deepseek"
|
|
84
|
+
}
|
|
85
|
+
]
|