active_harness 0.2.9 → 0.2.10
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/active_harness/http/streaming_client.rb +3 -1
- data/lib/active_harness/providers/base.rb +15 -0
- data/lib/active_harness/providers/deepseek.rb +7 -8
- data/lib/active_harness/providers/gemini.rb +8 -14
- data/lib/active_harness/providers/gpustack.rb +7 -13
- data/lib/active_harness/providers/groq.rb +8 -14
- data/lib/active_harness/providers/mistral.rb +7 -8
- data/lib/active_harness/providers/ollama.rb +7 -13
- data/lib/active_harness/providers/openai.rb +8 -14
- data/lib/active_harness/providers/openrouter.rb +1 -5
- data/lib/active_harness/providers/perplexity.rb +7 -8
- data/lib/active_harness/providers/xai.rb +7 -8
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 79db5ea3cf1416b2d9e8d3958d9a6e69c9ab65ea36ddf52bcdd8e41ff2e07b0c
|
|
4
|
+
data.tar.gz: 0fde20074cb5d215226fdac581b84220ae2d5462e14cae4bfc6e1216dff84164
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f6ccb47cc9f4c176d44503323ee642e879c66961521d69b102616ca92be8eeb8c0b38ce12e72bab1dbf67756c2d984edb8fb238d26778b4755f49370290a7861
|
|
7
|
+
data.tar.gz: 0c0478912520ed1a466a16dd4f453996c25f20878a6d1b10399faf89a7668fbfac58f0ef00d72c39b10db442f54366d0f886f1e70eb9908cc4bbbf6035412e0f
|
|
@@ -25,6 +25,7 @@ module ActiveHarness
|
|
|
25
25
|
|
|
26
26
|
buffer = ""
|
|
27
27
|
content = ""
|
|
28
|
+
usage = nil
|
|
28
29
|
|
|
29
30
|
http.request(req) do |response|
|
|
30
31
|
response.read_body do |chunk|
|
|
@@ -42,11 +43,12 @@ module ActiveHarness
|
|
|
42
43
|
on_token.call(token)
|
|
43
44
|
content += token
|
|
44
45
|
end
|
|
46
|
+
usage ||= parsed["usage"] if parsed.key?("usage")
|
|
45
47
|
end
|
|
46
48
|
end
|
|
47
49
|
end
|
|
48
50
|
|
|
49
|
-
content
|
|
51
|
+
{ content: content, raw_usage: usage }
|
|
50
52
|
rescue Net::OpenTimeout, Net::ReadTimeout
|
|
51
53
|
raise Errors::TimeoutError, "Request to #{url.host} timed out"
|
|
52
54
|
rescue JSON::ParserError
|
|
@@ -45,6 +45,21 @@ module ActiveHarness
|
|
|
45
45
|
}
|
|
46
46
|
end
|
|
47
47
|
|
|
48
|
+
# Streaming call for OpenAI-compatible providers.
|
|
49
|
+
# Adds stream: true and stream_options to body, calls StreamingClient,
|
|
50
|
+
# and returns the same { content:, provider:, model:, usage: } shape
|
|
51
|
+
# as non-streaming calls so callers need no special handling.
|
|
52
|
+
def call_streaming(url:, headers:, body:, stream:, provider:, model:)
|
|
53
|
+
body = body.merge(stream: true, stream_options: { include_usage: true })
|
|
54
|
+
result = post_json_stream(URI(url), headers: headers, body: body, on_token: stream)
|
|
55
|
+
{
|
|
56
|
+
content: result[:content],
|
|
57
|
+
provider: provider,
|
|
58
|
+
model: model,
|
|
59
|
+
usage: extract_usage_openai({ "usage" => result[:raw_usage] })
|
|
60
|
+
}
|
|
61
|
+
end
|
|
62
|
+
|
|
48
63
|
def parse!(raw)
|
|
49
64
|
JSON.parse(raw)
|
|
50
65
|
rescue JSON::ParserError => e
|
|
@@ -5,14 +5,13 @@ module ActiveHarness
|
|
|
5
5
|
# DeepSeek — OpenAI-compatible API.
|
|
6
6
|
# https://platform.deepseek.com/api-docs
|
|
7
7
|
class DeepSeek < Base
|
|
8
|
-
def call(model:, messages:, temperature: 0.7)
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
)
|
|
8
|
+
def call(model:, messages:, temperature: 0.7, stream: nil)
|
|
9
|
+
headers = { "Content-Type" => "application/json", "Authorization" => "Bearer #{api_key}" }
|
|
10
|
+
body = { model: model, messages: messages, temperature: temperature }
|
|
11
|
+
|
|
12
|
+
return call_streaming(url: config.deepseek_api_url, headers: headers, body: body, stream: stream, provider: :deepseek, model: model) if stream
|
|
13
|
+
|
|
14
|
+
raw = post_json(URI(config.deepseek_api_url), headers: headers, body: body)
|
|
16
15
|
data = parse!(raw)
|
|
17
16
|
handle_error!(data)
|
|
18
17
|
|
|
@@ -5,23 +5,17 @@ module ActiveHarness
|
|
|
5
5
|
# Google Gemini — OpenAI-compatible endpoint (beta).
|
|
6
6
|
# https://ai.google.dev/gemini-api/docs/openai
|
|
7
7
|
class Gemini < Base
|
|
8
|
-
def call(model:, messages:, temperature: 0.7)
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
)
|
|
8
|
+
def call(model:, messages:, temperature: 0.7, stream: nil)
|
|
9
|
+
headers = { "Content-Type" => "application/json", "Authorization" => "Bearer #{api_key}" }
|
|
10
|
+
body = { model: model, messages: messages, temperature: temperature }
|
|
11
|
+
|
|
12
|
+
return call_streaming(url: config.gemini_api_url, headers: headers, body: body, stream: stream, provider: :gemini, model: model) if stream
|
|
13
|
+
|
|
14
|
+
raw = post_json(URI(config.gemini_api_url), headers: headers, body: body)
|
|
16
15
|
data = parse!(raw)
|
|
17
16
|
handle_error!(data)
|
|
18
17
|
|
|
19
|
-
{
|
|
20
|
-
content: data.dig("choices", 0, "message", "content").to_s.strip,
|
|
21
|
-
provider: :gemini,
|
|
22
|
-
model: data["model"] || model,
|
|
23
|
-
usage: extract_usage_openai(data)
|
|
24
|
-
}
|
|
18
|
+
{ content: data.dig("choices", 0, "message", "content").to_s.strip, provider: :gemini, model: data["model"] || model, usage: extract_usage_openai(data) }
|
|
25
19
|
end
|
|
26
20
|
|
|
27
21
|
private
|
|
@@ -13,26 +13,20 @@ module ActiveHarness
|
|
|
13
13
|
# use provider: :gpustack, model: "Qwen/Qwen2.5-7B-Instruct-GGUF"
|
|
14
14
|
# end
|
|
15
15
|
class GPUStack < Base
|
|
16
|
-
def call(model:, messages:, temperature: 0.7)
|
|
17
|
-
url
|
|
18
|
-
|
|
16
|
+
def call(model:, messages:, temperature: 0.7, stream: nil)
|
|
17
|
+
url = "#{api_base}/v1/chat/completions"
|
|
19
18
|
headers = { "Content-Type" => "application/json" }
|
|
20
19
|
key = api_key
|
|
21
20
|
headers["Authorization"] = "Bearer #{key}" if key
|
|
21
|
+
body = { model: model, messages: messages, temperature: temperature }
|
|
22
|
+
|
|
23
|
+
return call_streaming(url: url, headers: headers, body: body, stream: stream, provider: :gpustack, model: model) if stream
|
|
22
24
|
|
|
23
|
-
raw = post_json(url,
|
|
24
|
-
headers: headers,
|
|
25
|
-
body: { model: model, messages: messages, temperature: temperature }
|
|
26
|
-
)
|
|
25
|
+
raw = post_json(URI(url), headers: headers, body: body)
|
|
27
26
|
data = parse!(raw)
|
|
28
27
|
handle_error!(data)
|
|
29
28
|
|
|
30
|
-
{
|
|
31
|
-
content: data.dig("choices", 0, "message", "content").to_s.strip,
|
|
32
|
-
provider: :gpustack,
|
|
33
|
-
model: data["model"] || model,
|
|
34
|
-
usage: extract_usage_openai(data)
|
|
35
|
-
}
|
|
29
|
+
{ content: data.dig("choices", 0, "message", "content").to_s.strip, provider: :gpustack, model: data["model"] || model, usage: extract_usage_openai(data) }
|
|
36
30
|
end
|
|
37
31
|
|
|
38
32
|
private
|
|
@@ -5,23 +5,17 @@ module ActiveHarness
|
|
|
5
5
|
# Groq — OpenAI-compatible API with fast inference.
|
|
6
6
|
# https://console.groq.com/docs/openai
|
|
7
7
|
class Groq < Base
|
|
8
|
-
def call(model:, messages:, temperature: 0.7)
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
)
|
|
8
|
+
def call(model:, messages:, temperature: 0.7, stream: nil)
|
|
9
|
+
headers = { "Content-Type" => "application/json", "Authorization" => "Bearer #{api_key}" }
|
|
10
|
+
body = { model: model, messages: messages, temperature: temperature }
|
|
11
|
+
|
|
12
|
+
return call_streaming(url: config.groq_api_url, headers: headers, body: body, stream: stream, provider: :groq, model: model) if stream
|
|
13
|
+
|
|
14
|
+
raw = post_json(URI(config.groq_api_url), headers: headers, body: body)
|
|
16
15
|
data = parse!(raw)
|
|
17
16
|
handle_error!(data)
|
|
18
17
|
|
|
19
|
-
{
|
|
20
|
-
content: data.dig("choices", 0, "message", "content").to_s.strip,
|
|
21
|
-
provider: :groq,
|
|
22
|
-
model: data["model"] || model,
|
|
23
|
-
usage: extract_usage_openai(data)
|
|
24
|
-
}
|
|
18
|
+
{ content: data.dig("choices", 0, "message", "content").to_s.strip, provider: :groq, model: data["model"] || model, usage: extract_usage_openai(data) }
|
|
25
19
|
end
|
|
26
20
|
|
|
27
21
|
private
|
|
@@ -5,14 +5,13 @@ module ActiveHarness
|
|
|
5
5
|
# Mistral AI — OpenAI-compatible API.
|
|
6
6
|
# https://docs.mistral.ai/api
|
|
7
7
|
class Mistral < Base
|
|
8
|
-
def call(model:, messages:, temperature: 0.7)
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
)
|
|
8
|
+
def call(model:, messages:, temperature: 0.7, stream: nil)
|
|
9
|
+
headers = { "Content-Type" => "application/json", "Authorization" => "Bearer #{api_key}" }
|
|
10
|
+
body = { model: model, messages: messages, temperature: temperature }
|
|
11
|
+
|
|
12
|
+
return call_streaming(url: config.mistral_api_url, headers: headers, body: body, stream: stream, provider: :mistral, model: model) if stream
|
|
13
|
+
|
|
14
|
+
raw = post_json(URI(config.mistral_api_url), headers: headers, body: body)
|
|
16
15
|
data = parse!(raw)
|
|
17
16
|
handle_error!(data)
|
|
18
17
|
|
|
@@ -13,26 +13,20 @@ module ActiveHarness
|
|
|
13
13
|
# use provider: :ollama, model: "llama3.2"
|
|
14
14
|
# end
|
|
15
15
|
class Ollama < Base
|
|
16
|
-
def call(model:, messages:, temperature: 0.7)
|
|
17
|
-
url
|
|
18
|
-
|
|
16
|
+
def call(model:, messages:, temperature: 0.7, stream: nil)
|
|
17
|
+
url = "#{api_base}/v1/chat/completions"
|
|
19
18
|
headers = { "Content-Type" => "application/json" }
|
|
20
19
|
key = api_key
|
|
21
20
|
headers["Authorization"] = "Bearer #{key}" if key
|
|
21
|
+
body = { model: model, messages: messages, temperature: temperature }
|
|
22
|
+
|
|
23
|
+
return call_streaming(url: url, headers: headers, body: body, stream: stream, provider: :ollama, model: model) if stream
|
|
22
24
|
|
|
23
|
-
raw = post_json(url,
|
|
24
|
-
headers: headers,
|
|
25
|
-
body: { model: model, messages: messages, temperature: temperature }
|
|
26
|
-
)
|
|
25
|
+
raw = post_json(URI(url), headers: headers, body: body)
|
|
27
26
|
data = parse!(raw)
|
|
28
27
|
handle_error!(data)
|
|
29
28
|
|
|
30
|
-
{
|
|
31
|
-
content: data.dig("choices", 0, "message", "content").to_s.strip,
|
|
32
|
-
provider: :ollama,
|
|
33
|
-
model: data["model"] || model,
|
|
34
|
-
usage: extract_usage_openai(data)
|
|
35
|
-
}
|
|
29
|
+
{ content: data.dig("choices", 0, "message", "content").to_s.strip, provider: :ollama, model: data["model"] || model, usage: extract_usage_openai(data) }
|
|
36
30
|
end
|
|
37
31
|
|
|
38
32
|
private
|
|
@@ -7,23 +7,17 @@ module ActiveHarness
|
|
|
7
7
|
# @param messages [Array<Hash>] [{role:, content:}, ...]
|
|
8
8
|
# @param temperature [Float]
|
|
9
9
|
# @return [Hash] { content:, provider:, model: }
|
|
10
|
-
def call(model:, messages:, temperature: 0.7)
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
)
|
|
10
|
+
def call(model:, messages:, temperature: 0.7, stream: nil)
|
|
11
|
+
headers = { "Content-Type" => "application/json", "Authorization" => "Bearer #{api_key}" }
|
|
12
|
+
body = { model: model, messages: messages, temperature: temperature }
|
|
13
|
+
|
|
14
|
+
return call_streaming(url: config.openai_api_url, headers: headers, body: body, stream: stream, provider: :openai, model: model) if stream
|
|
15
|
+
|
|
16
|
+
raw = post_json(URI(config.openai_api_url), headers: headers, body: body)
|
|
18
17
|
data = parse!(raw)
|
|
19
18
|
handle_error!(data)
|
|
20
19
|
|
|
21
|
-
{
|
|
22
|
-
content: data.dig("choices", 0, "message", "content").to_s.strip,
|
|
23
|
-
provider: :openai,
|
|
24
|
-
model: data["model"] || model,
|
|
25
|
-
usage: extract_usage_openai(data)
|
|
26
|
-
}
|
|
20
|
+
{ content: data.dig("choices", 0, "message", "content").to_s.strip, provider: :openai, model: data["model"] || model, usage: extract_usage_openai(data) }
|
|
27
21
|
end
|
|
28
22
|
|
|
29
23
|
private
|
|
@@ -17,11 +17,7 @@ module ActiveHarness
|
|
|
17
17
|
}
|
|
18
18
|
body = { model: model, messages: messages, temperature: temperature }
|
|
19
19
|
|
|
20
|
-
if stream
|
|
21
|
-
body[:stream] = true
|
|
22
|
-
content = post_json_stream(URI(config.openrouter_api_url), headers: headers, body: body, on_token: stream)
|
|
23
|
-
return { content: content, provider: :openrouter, model: model }
|
|
24
|
-
end
|
|
20
|
+
return call_streaming(url: config.openrouter_api_url, headers: headers, body: body, stream: stream, provider: :openrouter, model: model) if stream
|
|
25
21
|
|
|
26
22
|
raw = post_json(URI(config.openrouter_api_url), headers: headers, body: body)
|
|
27
23
|
data = parse!(raw)
|
|
@@ -5,14 +5,13 @@ module ActiveHarness
|
|
|
5
5
|
# Perplexity — OpenAI-compatible API with web-search-augmented models.
|
|
6
6
|
# https://docs.perplexity.ai/api-reference/chat-completions
|
|
7
7
|
class Perplexity < Base
|
|
8
|
-
def call(model:, messages:, temperature: 0.7)
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
)
|
|
8
|
+
def call(model:, messages:, temperature: 0.7, stream: nil)
|
|
9
|
+
headers = { "Content-Type" => "application/json", "Authorization" => "Bearer #{api_key}" }
|
|
10
|
+
body = { model: model, messages: messages, temperature: temperature }
|
|
11
|
+
|
|
12
|
+
return call_streaming(url: config.perplexity_api_url, headers: headers, body: body, stream: stream, provider: :perplexity, model: model) if stream
|
|
13
|
+
|
|
14
|
+
raw = post_json(URI(config.perplexity_api_url), headers: headers, body: body)
|
|
16
15
|
data = parse!(raw)
|
|
17
16
|
handle_error!(data)
|
|
18
17
|
|
|
@@ -5,14 +5,13 @@ module ActiveHarness
|
|
|
5
5
|
# xAI (Grok) — OpenAI-compatible API.
|
|
6
6
|
# https://docs.x.ai/api
|
|
7
7
|
class XAI < Base
|
|
8
|
-
def call(model:, messages:, temperature: 0.7)
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
)
|
|
8
|
+
def call(model:, messages:, temperature: 0.7, stream: nil)
|
|
9
|
+
headers = { "Content-Type" => "application/json", "Authorization" => "Bearer #{api_key}" }
|
|
10
|
+
body = { model: model, messages: messages, temperature: temperature }
|
|
11
|
+
|
|
12
|
+
return call_streaming(url: config.xai_api_url, headers: headers, body: body, stream: stream, provider: :xai, model: model) if stream
|
|
13
|
+
|
|
14
|
+
raw = post_json(URI(config.xai_api_url), headers: headers, body: body)
|
|
16
15
|
data = parse!(raw)
|
|
17
16
|
handle_error!(data)
|
|
18
17
|
|