raimei-nim 0.1.1 → 0.1.3
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/Gemfile +15 -0
- data/exe/raimei-chat +29 -0
- data/lib/raimei/nim/version.rb +1 -1
- data/lib/raimei/nim.rb +56 -12
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 676783e23d825f284282d6a366566b0a4029e7314e87c23524841e4a3dbcbd6c
|
4
|
+
data.tar.gz: b86cd974c8ab09e1c46ad49fc0c6f2acaa8c6f7f704de5aeb014415ecf18ec2f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e39c9f8dcd33f14ef651eda0228145c1136b5ae23e099f2338ee3697f9905094b45917fab77476a674519f3a078605406a6253c768a9c5514f4844577970dbcb
|
7
|
+
data.tar.gz: 1e7bc601c8dd19f8d53245841812d29cfafe8034c098c820219bf2417a4f5ef46e82f3ff0e3f034e834518ef1db6b2a499977cc3c33c6d1f541897aaff841f2d
|
data/Gemfile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
source "https://rubygems.org"
|
4
|
+
|
5
|
+
# Specify your gem's dependencies in raimei-nim.gemspec
|
6
|
+
gemspec
|
7
|
+
|
8
|
+
gem "irb"
|
9
|
+
gem "rake", "~> 13.0"
|
10
|
+
|
11
|
+
gem "minitest", "~> 5.16"
|
12
|
+
|
13
|
+
gem "rubocop", "~> 1.21"
|
14
|
+
|
15
|
+
gem "webrick", "~> 1.8"
|
data/exe/raimei-chat
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# frozen_string_literal: true
|
4
|
+
|
5
|
+
require "optparse"
|
6
|
+
require "raimei/nim"
|
7
|
+
|
8
|
+
opts = { url: ENV["URL"], model: ENV["MODEL"] || "llama3", api_key: ENV["API_KEY"], stream: true }
|
9
|
+
OptionParser.new do |o|
|
10
|
+
o.banner = "Usage: raimei-chat [--url URL] [--model MODEL] [--api-key KEY] [--no-stream] [prompt...]\n" \
|
11
|
+
"Reads from ARGV or stdin."
|
12
|
+
o.on("--url URL") { |v| opts[:url] = v }
|
13
|
+
o.on("--model MODEL") { |v| opts[:model] = v }
|
14
|
+
o.on("--api-key KEY") { |v| opts[:api_key] = v }
|
15
|
+
o.on("--no-stream") { opts[:stream] = false }
|
16
|
+
end.parse!
|
17
|
+
|
18
|
+
abort "Set --url or URL env" unless opts[:url]
|
19
|
+
prompt = if !STDIN.tty? && ARGV.empty? then STDIN.read else ARGV.join(" ") end
|
20
|
+
abort "Provide a prompt (args or stdin)" if prompt.to_s.strip.empty?
|
21
|
+
|
22
|
+
client = Raimei::NIM::Client.new(url: opts[:url], api_key: opts[:api_key])
|
23
|
+
if opts[:stream]
|
24
|
+
client.chat(model: opts[:model], messages: [{ role: "user", content: prompt }], stream: true).each { |d| print d }
|
25
|
+
puts
|
26
|
+
else
|
27
|
+
puts client.chat(model: opts[:model], messages: [{ role: "user", content: prompt }], stream: false)
|
28
|
+
end
|
29
|
+
|
data/lib/raimei/nim/version.rb
CHANGED
data/lib/raimei/nim.rb
CHANGED
@@ -1,31 +1,75 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "json"
|
4
|
-
require "net/http"
|
5
|
-
require "uri"
|
3
|
+
require "json"; require "net/http"; require "uri"
|
6
4
|
|
7
5
|
module Raimei
|
8
6
|
module NIM
|
9
7
|
class Client
|
10
|
-
def initialize(url:, api_key: nil, timeout: 60)
|
11
|
-
@uri = URI(url)
|
12
|
-
@api_key = api_key
|
13
|
-
@timeout = timeout
|
8
|
+
def initialize(url:, api_key: nil, timeout: 60, retries: 2)
|
9
|
+
@uri = URI(url); @api_key = api_key; @timeout = timeout; @retries = retries
|
14
10
|
end
|
15
11
|
|
16
12
|
# messages: [{role:"user", content:"Hi"}]
|
17
|
-
|
18
|
-
|
13
|
+
# stream: true -> Enumerator (or yield chunks if block given)
|
14
|
+
# stream: false -> String
|
15
|
+
def chat(model:, messages:, stream: true, **opts, &blk)
|
16
|
+
body = { model: model, messages: messages, stream: stream }.merge(opts)
|
19
17
|
req = Net::HTTP::Post.new(@uri)
|
20
18
|
req["Content-Type"] = "application/json"
|
19
|
+
req["Accept"] = stream ? "text/event-stream" : "application/json"
|
21
20
|
req["Authorization"] = "Bearer #{@api_key}" if @api_key
|
22
21
|
req.body = JSON.dump(body)
|
23
22
|
|
24
|
-
|
23
|
+
with_retries do
|
24
|
+
if stream
|
25
|
+
if block_given?
|
26
|
+
_sse(req) { |delta| blk.call(delta) }
|
27
|
+
else
|
28
|
+
Enumerator.new { |y| _sse(req) { |delta| y << delta } }
|
29
|
+
end
|
30
|
+
else
|
31
|
+
_json(req)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def with_retries
|
39
|
+
attempts = 0
|
40
|
+
begin
|
41
|
+
return yield
|
42
|
+
rescue => e
|
43
|
+
attempts += 1
|
44
|
+
raise e if attempts > @retries
|
45
|
+
sleep(0.5 * attempts) # tiny backoff
|
46
|
+
retry
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def _json(req)
|
51
|
+
Net::HTTP.start(@uri.host, @uri.port, use_ssl: @uri.scheme=="https", read_timeout:@timeout) do |http|
|
25
52
|
res = http.request(req)
|
26
53
|
raise "HTTP #{res.code}: #{res.body}" unless res.is_a?(Net::HTTPSuccess)
|
27
|
-
|
28
|
-
|
54
|
+
j = JSON.parse(res.body)
|
55
|
+
j.dig("choices",0,"message","content").to_s
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def _sse(req)
|
60
|
+
Net::HTTP.start(@uri.host, @uri.port, use_ssl:@uri.scheme=="https", read_timeout:@timeout) do |http|
|
61
|
+
http.request(req) do |res|
|
62
|
+
res.read_body do |chunk|
|
63
|
+
chunk.each_line do |line|
|
64
|
+
next unless line.start_with?("data:")
|
65
|
+
data = line.sub("data:","").strip
|
66
|
+
break if data == "[DONE]"
|
67
|
+
json = JSON.parse(data) rescue nil
|
68
|
+
next unless json
|
69
|
+
yield(json.dig("choices",0,"delta","content") || "")
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
29
73
|
end
|
30
74
|
end
|
31
75
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: raimei-nim
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Hal Fulton
|
@@ -13,14 +13,17 @@ description: Streaming SSE client with timeouts/retries; works with NIM/TRT-LLM,
|
|
13
13
|
Ollama, and other OpenAI-style servers.
|
14
14
|
email:
|
15
15
|
- rubyhacker@gmail.com
|
16
|
-
executables:
|
16
|
+
executables:
|
17
|
+
- raimei-chat
|
17
18
|
extensions: []
|
18
19
|
extra_rdoc_files: []
|
19
20
|
files:
|
20
21
|
- CHANGELOG.md
|
22
|
+
- Gemfile
|
21
23
|
- LICENSE.txt
|
22
24
|
- README.md
|
23
25
|
- Rakefile
|
26
|
+
- exe/raimei-chat
|
24
27
|
- lib/raimei/nim.rb
|
25
28
|
- lib/raimei/nim/version.rb
|
26
29
|
- sig/raimei/nim.rbs
|