ask-llm-providers 0.1.15 → 0.1.16
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/lib/ask/llm/sse_buffer.rb +43 -0
- data/lib/ask/llm/version.rb +1 -1
- data/lib/ask/provider/anthropic.rb +17 -30
- data/lib/ask/provider/cloudflare.rb +3 -5
- data/lib/ask/provider/google.rb +3 -4
- data/lib/ask/provider/ollama.rb +8 -1
- data/lib/ask/provider/openai.rb +3 -5
- data/lib/ask-llm-providers.rb +1 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 93b16f8655292838315d314b3a57dd94368182372a421f88b3911d6a9b171488
|
|
4
|
+
data.tar.gz: 59fe887341f72aa42c765da7bda10e93454aa8b8cc25bfd1ebb85e316d401a17
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8627f783b0e7848373fd9358c13e48db978fe728b043c7c5e9f23fd4425c631203c19eae980d870e1f683cdd16d803199cf7b8cba7b8bb0b5f4bfe7b09ffc1ad
|
|
7
|
+
data.tar.gz: 13cbee9ed69bca82917cbfd060e6f8f8a5130c717b975b886f3851203dada60da1ac081200c2613efa53b7625e744baedc639e06e2ee60bc990faa91c14bccfa
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ask
|
|
4
|
+
module LLM
|
|
5
|
+
module SSEBuffer
|
|
6
|
+
def init_sse_buffer
|
|
7
|
+
@_sse_buffer = +""
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def each_sse_event(raw)
|
|
11
|
+
@_sse_buffer ||= +""
|
|
12
|
+
@_sse_buffer << raw
|
|
13
|
+
|
|
14
|
+
while (event_end = @_sse_buffer.index("\n\n"))
|
|
15
|
+
event_data = @_sse_buffer.slice!(0, event_end + 2).strip
|
|
16
|
+
next if event_data.empty?
|
|
17
|
+
|
|
18
|
+
data_content = extract_data(event_data)
|
|
19
|
+
next if data_content.empty?
|
|
20
|
+
break if data_content == "[DONE]"
|
|
21
|
+
|
|
22
|
+
yield data_content
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def extract_data(event_data)
|
|
29
|
+
content = +""
|
|
30
|
+
event_data.each_line do |line|
|
|
31
|
+
line = line.strip
|
|
32
|
+
next if line.empty? || line.start_with?(":")
|
|
33
|
+
if line.start_with?("data: ")
|
|
34
|
+
content << line[6..]
|
|
35
|
+
elsif line.start_with?("data:")
|
|
36
|
+
content << line[5..]
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
content
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
data/lib/ask/llm/version.rb
CHANGED
|
@@ -4,6 +4,7 @@ module Ask
|
|
|
4
4
|
module Providers
|
|
5
5
|
# Anthropic Claude API provider.
|
|
6
6
|
class Anthropic < Ask::Provider
|
|
7
|
+
include Ask::LLM::SSEBuffer
|
|
7
8
|
def initialize(config = {})
|
|
8
9
|
config = normalize_config(config)
|
|
9
10
|
super(config)
|
|
@@ -179,6 +180,7 @@ module Ask
|
|
|
179
180
|
|
|
180
181
|
def chat_stream(payload, model, &block)
|
|
181
182
|
stream = Ask::Stream.new
|
|
183
|
+
init_sse_buffer
|
|
182
184
|
response = @http.post("v1/messages") do |req|
|
|
183
185
|
req.body = payload.merge(stream: true)
|
|
184
186
|
req.options.on_data = proc { |data, _bytes, _env| process_anthropic_chunk(data, stream, model, &block) }
|
|
@@ -189,39 +191,24 @@ module Ask
|
|
|
189
191
|
end
|
|
190
192
|
|
|
191
193
|
def process_anthropic_chunk(raw, stream, model)
|
|
192
|
-
raw
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
when "content_block_delta"
|
|
207
|
-
delta = parsed.dig("delta")
|
|
208
|
-
next unless delta
|
|
209
|
-
chunk = Ask::Chunk.new(
|
|
210
|
-
content: delta["text"],
|
|
211
|
-
finish_reason: delta["type"] == "thinking_delta" ? nil : nil
|
|
212
|
-
)
|
|
194
|
+
each_sse_event(raw) do |data|
|
|
195
|
+
parsed = JSON.parse(data) rescue next
|
|
196
|
+
|
|
197
|
+
case parsed["type"]
|
|
198
|
+
when "content_block_delta"
|
|
199
|
+
delta = parsed.dig("delta")
|
|
200
|
+
next unless delta
|
|
201
|
+
chunk = Ask::Chunk.new(content: delta["text"])
|
|
202
|
+
stream.add(chunk)
|
|
203
|
+
yield chunk if block_given?
|
|
204
|
+
when "message_stop"
|
|
205
|
+
usage = parsed["usage"] || parsed["message"]&.dig("usage")
|
|
206
|
+
if usage
|
|
207
|
+
chunk = Ask::Chunk.new(finish_reason: "stop", usage: usage)
|
|
213
208
|
stream.add(chunk)
|
|
214
209
|
yield chunk if block_given?
|
|
215
|
-
when "message_stop"
|
|
216
|
-
usage = parsed["usage"] || parsed["message"]&.dig("usage")
|
|
217
|
-
if usage
|
|
218
|
-
chunk = Ask::Chunk.new(finish_reason: "stop", usage: usage)
|
|
219
|
-
stream.add(chunk)
|
|
220
|
-
yield chunk if block_given?
|
|
221
|
-
end
|
|
222
|
-
when "message_start"
|
|
223
|
-
# Message started — no content yet
|
|
224
210
|
end
|
|
211
|
+
when "message_start"
|
|
225
212
|
end
|
|
226
213
|
end
|
|
227
214
|
end
|
|
@@ -4,6 +4,7 @@ module Ask
|
|
|
4
4
|
module Providers
|
|
5
5
|
# Cloudflare Workers AI provider. Supports both direct Workers AI and AI Gateway.
|
|
6
6
|
class Cloudflare < Ask::Provider
|
|
7
|
+
include Ask::LLM::SSEBuffer
|
|
7
8
|
def initialize(config = {})
|
|
8
9
|
config = normalize_config(config)
|
|
9
10
|
super(config)
|
|
@@ -96,6 +97,7 @@ module Ask
|
|
|
96
97
|
|
|
97
98
|
def chat_stream_gateway(endpoint, payload, model, &block)
|
|
98
99
|
stream = Ask::Stream.new
|
|
100
|
+
init_sse_buffer
|
|
99
101
|
response = @http.post(endpoint) do |req|
|
|
100
102
|
req.body = payload.merge(stream: true)
|
|
101
103
|
req.options.on_data = proc { |data, _bytes, _env| process_stream_chunk(data, stream, model, &block) }
|
|
@@ -106,11 +108,7 @@ module Ask
|
|
|
106
108
|
end
|
|
107
109
|
|
|
108
110
|
def process_stream_chunk(raw, stream, model)
|
|
109
|
-
raw
|
|
110
|
-
line = line.strip
|
|
111
|
-
next unless line.start_with?("data: ")
|
|
112
|
-
data = line[6..]
|
|
113
|
-
next if data == "[DONE]"
|
|
111
|
+
each_sse_event(raw) do |data|
|
|
114
112
|
parsed = JSON.parse(data) rescue next
|
|
115
113
|
delta = parsed.dig("choices", 0, "delta") || {}
|
|
116
114
|
chunk = Ask::Chunk.new(content: delta["content"])
|
data/lib/ask/provider/google.rb
CHANGED
|
@@ -4,6 +4,7 @@ module Ask
|
|
|
4
4
|
module Providers
|
|
5
5
|
# Google Gemini API provider. Also supports Vertex AI via GCP service account auth.
|
|
6
6
|
class Google < Ask::Provider
|
|
7
|
+
include Ask::LLM::SSEBuffer
|
|
7
8
|
def initialize(config = {})
|
|
8
9
|
config = normalize_config(config)
|
|
9
10
|
super(config)
|
|
@@ -187,6 +188,7 @@ module Ask
|
|
|
187
188
|
|
|
188
189
|
def chat_stream(path, payload, model, &block)
|
|
189
190
|
stream = Ask::Stream.new
|
|
191
|
+
init_sse_buffer
|
|
190
192
|
response = @http.post(path) do |req|
|
|
191
193
|
req.body = payload
|
|
192
194
|
req.params["key"] = @config.api_key if @config.api_key
|
|
@@ -198,10 +200,7 @@ module Ask
|
|
|
198
200
|
end
|
|
199
201
|
|
|
200
202
|
def process_google_chunk(raw, stream, model)
|
|
201
|
-
raw
|
|
202
|
-
next unless line.start_with?("data: ")
|
|
203
|
-
data = line[6..]
|
|
204
|
-
next if data.strip == "[DONE]"
|
|
203
|
+
each_sse_event(raw) do |data|
|
|
205
204
|
parsed = JSON.parse(data) rescue next
|
|
206
205
|
candidate = parsed.dig("candidates", 0) or next
|
|
207
206
|
part = candidate.dig("content", "parts", 0)
|
data/lib/ask/provider/ollama.rb
CHANGED
|
@@ -79,6 +79,7 @@ module Ask
|
|
|
79
79
|
|
|
80
80
|
def chat_stream(payload, model, &block)
|
|
81
81
|
stream = Ask::Stream.new
|
|
82
|
+
@_sse_buffer = +""
|
|
82
83
|
response = @http.post("api/chat") do |req|
|
|
83
84
|
req.body = payload.merge(stream: true)
|
|
84
85
|
req.options.on_data = proc { |data, _bytes, _env| process_ollama_chunk(data, stream, model, &block) }
|
|
@@ -89,7 +90,13 @@ module Ask
|
|
|
89
90
|
end
|
|
90
91
|
|
|
91
92
|
def process_ollama_chunk(raw, stream, model)
|
|
92
|
-
|
|
93
|
+
@_sse_buffer ||= +""
|
|
94
|
+
@_sse_buffer << raw
|
|
95
|
+
|
|
96
|
+
while (line_end = @_sse_buffer.index("\n"))
|
|
97
|
+
line = @_sse_buffer.slice!(0, line_end + 1).strip
|
|
98
|
+
next if line.empty?
|
|
99
|
+
|
|
93
100
|
parsed = JSON.parse(line) rescue next
|
|
94
101
|
msg = parsed["message"] || {}
|
|
95
102
|
chunk = Ask::Chunk.new(content: msg["content"])
|
data/lib/ask/provider/openai.rb
CHANGED
|
@@ -6,6 +6,7 @@ module Ask
|
|
|
6
6
|
# (OpenRouter, DeepSeek, Azure, XAI, Perplexity, GPUStack, etc.) via
|
|
7
7
|
# +base_url+ override.
|
|
8
8
|
class OpenAI < Ask::Provider
|
|
9
|
+
include Ask::LLM::SSEBuffer
|
|
9
10
|
def initialize(config = {})
|
|
10
11
|
@provider_keys = extract_provider_keys(config)
|
|
11
12
|
config = normalize_config(config)
|
|
@@ -148,6 +149,7 @@ module Ask
|
|
|
148
149
|
|
|
149
150
|
def chat_stream(payload, model, &block)
|
|
150
151
|
stream = Ask::Stream.new
|
|
152
|
+
init_sse_buffer
|
|
151
153
|
@http.post("chat/completions") do |req|
|
|
152
154
|
req.body = payload.merge(stream: true)
|
|
153
155
|
req.options.on_data = proc { |data, _bytes, _env| process_chunk(data, stream, model, &block) }
|
|
@@ -167,12 +169,8 @@ module Ask
|
|
|
167
169
|
stream
|
|
168
170
|
end
|
|
169
171
|
|
|
170
|
-
|
|
171
172
|
def process_chunk(raw, stream, model)
|
|
172
|
-
raw
|
|
173
|
-
line = line.strip
|
|
174
|
-
next if line.empty? || line.start_with?(":") || !line.start_with?("data: ")
|
|
175
|
-
data = line[6..]; next if data == "[DONE]"
|
|
173
|
+
each_sse_event(raw) do |data|
|
|
176
174
|
parsed = JSON.parse(data) rescue next
|
|
177
175
|
choice = parsed.dig("choices", 0) or next
|
|
178
176
|
delta = choice["delta"] || {}
|
data/lib/ask-llm-providers.rb
CHANGED
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.
|
|
4
|
+
version: 0.1.16
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Kaka Ruto
|
|
@@ -178,6 +178,7 @@ files:
|
|
|
178
178
|
- lib/ask/llm/config.rb
|
|
179
179
|
- lib/ask/llm/http.rb
|
|
180
180
|
- lib/ask/llm/models/openai.rb
|
|
181
|
+
- lib/ask/llm/sse_buffer.rb
|
|
181
182
|
- lib/ask/llm/version.rb
|
|
182
183
|
- lib/ask/provider/anthropic.rb
|
|
183
184
|
- lib/ask/provider/bedrock.rb
|