llmclt 1.0.1 → 1.0.2
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/.rubocop.yml +3 -1
- data/CHANGELOG.md +9 -0
- data/README.md +41 -0
- data/lib/llmclt/client.rb +15 -3
- data/lib/llmclt/fetcher.rb +113 -0
- data/lib/llmclt/request/base.rb +44 -0
- data/lib/llmclt/request/batch.rb +51 -0
- data/lib/llmclt/request/chat.rb +13 -0
- data/lib/llmclt/request/non_streaming.rb +19 -0
- data/lib/llmclt/request/streaming.rb +19 -0
- data/lib/llmclt/response/base.rb +28 -0
- data/lib/llmclt/response/batch.rb +19 -0
- data/lib/llmclt/response/chat.rb +15 -0
- data/lib/llmclt/response/non_streaming.rb +15 -0
- data/lib/llmclt/response/streaming.rb +15 -0
- data/lib/llmclt/syntax/chat.rb +79 -0
- data/lib/llmclt/version.rb +1 -1
- data/lib/llmclt.rb +12 -2
- metadata +15 -5
- data/lib/llmclt/request.rb +0 -128
- data/lib/llmclt/response.rb +0 -46
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 624b382b4d3337d9225a4c4d6d6892372f5a60444528dd2fc6a2f151eea81fae
|
4
|
+
data.tar.gz: 8a76cd4a7be9ec84f145e099cd3c0418428f23b4d58364422ff9319dc31d70ab
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 276766c1a32fce5c867b82575e49ef8fb361c8acf9ca06063c7adacc9aac877f1cbc5e75e351dc09da52754daea576972bedb75be5ec0a237e8b88e68e7c4b79
|
7
|
+
data.tar.gz: 7f2cc43c7cb87bd1f032031c562c47c8fed6be5b899a5e2eea6c72ac8804fce1330e13f871803e7565a0abeadb12734d3f98acbc90db5d32f339d4658845a03a
|
data/.rubocop.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,14 @@
|
|
1
1
|
# CHANGELOG
|
2
2
|
|
3
|
+
## 1.0.2
|
4
|
+
|
5
|
+
- Rename a class from Request to Fetcher
|
6
|
+
- Separate request and response classes for each mode
|
7
|
+
- Add request jsonl output feature
|
8
|
+
- Add batch request feature
|
9
|
+
- Fix endpoint urls when location_id is global
|
10
|
+
- Change required ruby version from 2.7 to 3.1
|
11
|
+
|
3
12
|
## 1.0.1
|
4
13
|
|
5
14
|
- Add HTTP response code methods in Llmclt::Response
|
data/README.md
CHANGED
@@ -82,6 +82,47 @@ response = client.request(prompt, histories: histories)
|
|
82
82
|
response.text
|
83
83
|
```
|
84
84
|
|
85
|
+
### Output JSON Lines format
|
86
|
+
|
87
|
+
```ruby
|
88
|
+
client = Llmclt::Client.new()
|
89
|
+
prompt = [
|
90
|
+
'Hello. This is Tom!. How are you?',
|
91
|
+
'Do you know my name?'
|
92
|
+
]
|
93
|
+
client.chat_jsonl(prompts)
|
94
|
+
```
|
95
|
+
then, it outputs JSON Lines like as follows:
|
96
|
+
```json
|
97
|
+
{"request":{"contents": [{"role": "user", "parts": [{"text": "Hello. This is Tom!. How are you?"}]}]}}
|
98
|
+
{"request":{"contents": [{"role": "user", "parts": [{"text": "Do you know my name?"}]}]}}
|
99
|
+
```
|
100
|
+
|
101
|
+
### Batch mode
|
102
|
+
|
103
|
+
```ruby
|
104
|
+
client = Llmclt::Client.new(
|
105
|
+
project_id: project_id,
|
106
|
+
location_id: location_id,
|
107
|
+
model: model,
|
108
|
+
service_account_json: service_account_json
|
109
|
+
)
|
110
|
+
batch_job_name = 'BATCH_JOB_NAME'
|
111
|
+
gcs_input_uri = 'gs://BUCKET_NAME/file.jsonl'
|
112
|
+
gcs_output_uri = 'gs://BUCKET_NAME/'
|
113
|
+
response = client.batch_request(batch_job_name, gcs_input_uri, gcs_output_uri)
|
114
|
+
response.state
|
115
|
+
=> 'JOB_STATE_PENDING'
|
116
|
+
response.batch_job_id
|
117
|
+
=> 'BATCH_JOB_ID'
|
118
|
+
```
|
119
|
+
then, you can poll for the status of the batch job using the `BATCH_JOB_ID` like as follows:
|
120
|
+
```ruby
|
121
|
+
response = client.batch_state_request('BATCH_JOB_ID')
|
122
|
+
response.state
|
123
|
+
=> 'JOB_STATE_SUCCEEDED'
|
124
|
+
```
|
125
|
+
|
85
126
|
### Configuration
|
86
127
|
|
87
128
|
```ruby
|
data/lib/llmclt/client.rb
CHANGED
@@ -4,15 +4,27 @@ module Llmclt
|
|
4
4
|
class Client
|
5
5
|
def initialize(**kwargs)
|
6
6
|
@config = Llmclt::Config.new(**kwargs)
|
7
|
-
@
|
7
|
+
@fetcher = Llmclt::Fetcher.new(@config)
|
8
8
|
end
|
9
9
|
|
10
10
|
def request(prompt, stream: false, histories: [])
|
11
|
-
@
|
11
|
+
@fetcher.run(prompt, stream: stream, histories: histories)
|
12
|
+
end
|
13
|
+
|
14
|
+
def chat_jsonl(prompts, histories: [])
|
15
|
+
Llmclt::Syntax::Chat.new(prompts, @config, histories: histories).to_jsonl
|
16
|
+
end
|
17
|
+
|
18
|
+
def batch_request(batch_job_name, gcs_input_uri, gcs_output_uri)
|
19
|
+
@fetcher.batch_run(batch_job_name, gcs_input_uri, gcs_output_uri)
|
20
|
+
end
|
21
|
+
|
22
|
+
def batch_state_request(batch_job_id)
|
23
|
+
@fetcher.batch_state_run(batch_job_id)
|
12
24
|
end
|
13
25
|
|
14
26
|
def shutdown
|
15
|
-
@
|
27
|
+
@fetcher.shutdown
|
16
28
|
end
|
17
29
|
end
|
18
30
|
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Llmclt
|
4
|
+
class Fetcher
|
5
|
+
def initialize(config)
|
6
|
+
@config = config
|
7
|
+
@pool = Llmclt::Http::ConnectionPool.new
|
8
|
+
end
|
9
|
+
|
10
|
+
def run(prompt, stream: false, histories: [])
|
11
|
+
request = build_request(
|
12
|
+
request_mode(stream),
|
13
|
+
request_headers,
|
14
|
+
@config,
|
15
|
+
prompt,
|
16
|
+
histories
|
17
|
+
)
|
18
|
+
request_http(request)
|
19
|
+
end
|
20
|
+
|
21
|
+
def batch_run(batch_job_name, gcs_input_uri, gcs_output_uri)
|
22
|
+
request = Llmclt::Request::Batch.new(
|
23
|
+
request_headers,
|
24
|
+
@config,
|
25
|
+
batch_job_name: batch_job_name,
|
26
|
+
gcs_input_uri: gcs_input_uri,
|
27
|
+
gcs_output_uri: gcs_output_uri
|
28
|
+
)
|
29
|
+
request_http(request)
|
30
|
+
end
|
31
|
+
|
32
|
+
def batch_state_run(batch_job_id)
|
33
|
+
request = Llmclt::Request::Batch.new(
|
34
|
+
request_headers,
|
35
|
+
@config,
|
36
|
+
batch_job_id: batch_job_id
|
37
|
+
)
|
38
|
+
request_http(request)
|
39
|
+
end
|
40
|
+
|
41
|
+
def shutdown
|
42
|
+
@pool.shutdown
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def request_http(request)
|
48
|
+
http = checkout_http(request.endpoint_uri)
|
49
|
+
response = http.start { |h| h.request(request.content) }
|
50
|
+
request.build_response(response)
|
51
|
+
end
|
52
|
+
|
53
|
+
def request_mode(stream)
|
54
|
+
if stream
|
55
|
+
'streaming'
|
56
|
+
else
|
57
|
+
'non_streaming'
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def request_headers
|
62
|
+
authorizer.apply({ 'Content-Type': 'application/json' })
|
63
|
+
end
|
64
|
+
|
65
|
+
def authorizer
|
66
|
+
if @authorizer.nil? || @authorizer.expires_within?(60)
|
67
|
+
@authorizer = Google::Auth::ServiceAccountCredentials.make_creds(
|
68
|
+
json_key_io: StringIO.new(@config.service_account_json),
|
69
|
+
scope: 'https://www.googleapis.com/auth/cloud-platform'
|
70
|
+
)
|
71
|
+
@authorizer.fetch_access_token!
|
72
|
+
end
|
73
|
+
@authorizer
|
74
|
+
end
|
75
|
+
|
76
|
+
def build_request(request_mode, request_headers, config, prompt, histories)
|
77
|
+
case request_mode
|
78
|
+
when 'streaming'
|
79
|
+
Llmclt::Request::Streaming.new(
|
80
|
+
request_headers, config,
|
81
|
+
prompt: prompt,
|
82
|
+
histories: histories
|
83
|
+
)
|
84
|
+
when 'non_streaming'
|
85
|
+
Llmclt::Request::NonStreaming.new(
|
86
|
+
request_headers, config,
|
87
|
+
prompt: prompt,
|
88
|
+
histories: histories
|
89
|
+
)
|
90
|
+
else
|
91
|
+
raise NameError, "#{request_mode} is an invalid request mode"
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def checkout_http(uri)
|
96
|
+
@pool.checkout(pool_name(uri)) { build_http(uri) }
|
97
|
+
end
|
98
|
+
|
99
|
+
def pool_name(uri)
|
100
|
+
"#{uri.scheme}_#{uri.host}_#{uri.port || uri.default_port}"
|
101
|
+
end
|
102
|
+
|
103
|
+
def build_http(uri)
|
104
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
105
|
+
http.open_timeout = @config.open_timeout
|
106
|
+
http.read_timeout = @config.read_timeout
|
107
|
+
http.keep_alive_timeout = @config.keep_alive_timeout
|
108
|
+
http.use_ssl = true
|
109
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
110
|
+
http
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Llmclt
|
4
|
+
module Request
|
5
|
+
class Base
|
6
|
+
def initialize(headers, config, **kwargs)
|
7
|
+
@headers = headers
|
8
|
+
@config = config
|
9
|
+
kwargs.each { |k, v| instance_variable_set(:"@#{k}", v) }
|
10
|
+
end
|
11
|
+
|
12
|
+
def endpoint_uri
|
13
|
+
raise NotImplementedError, "#{class_name}#{__method__} is not implemented"
|
14
|
+
end
|
15
|
+
|
16
|
+
def content
|
17
|
+
request = Net::HTTP::Post.new(endpoint_uri.request_uri)
|
18
|
+
@headers.each do |key, value|
|
19
|
+
request[key] = value
|
20
|
+
end
|
21
|
+
request.body = build_request_json
|
22
|
+
request
|
23
|
+
end
|
24
|
+
|
25
|
+
def build_response(response)
|
26
|
+
raise NotImplementedError, "#{class_name}#{__method__} is not implemented"
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def endpoint_host
|
32
|
+
if @config.location_id == 'global'
|
33
|
+
'aiplatform.googleapis.com'
|
34
|
+
else
|
35
|
+
"#{@config.location_id}-aiplatform.googleapis.com"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def build_request_json
|
40
|
+
raise NotImplementedError, "#{class_name}#{__method__} is not implemented"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Llmclt
|
4
|
+
module Request
|
5
|
+
class Batch < Base
|
6
|
+
def endpoint_uri
|
7
|
+
url = "https://#{endpoint_host}/v1/projects/#{@config.project_id}/locations/#{@config.location_id}/batchPredictionJobs"
|
8
|
+
url = "#{url}/#{@batch_job_id}" if @batch_job_id
|
9
|
+
URI.parse(url)
|
10
|
+
end
|
11
|
+
|
12
|
+
def content
|
13
|
+
if @batch_job_id
|
14
|
+
request = Net::HTTP::Get.new(endpoint_uri.request_uri)
|
15
|
+
@headers.each do |key, value|
|
16
|
+
request[key] = value
|
17
|
+
end
|
18
|
+
request
|
19
|
+
else
|
20
|
+
super
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def build_response(response)
|
25
|
+
Llmclt::Response::Batch.new(response)
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def build_request_json
|
31
|
+
{
|
32
|
+
name: @batch_job_name,
|
33
|
+
displayName: @batch_job_name,
|
34
|
+
model: "publishers/google/models/#{@config.model}",
|
35
|
+
inputConfig: {
|
36
|
+
instancesFormat: 'jsonl',
|
37
|
+
gcsSource: {
|
38
|
+
uris: @gcs_input_uri
|
39
|
+
}
|
40
|
+
},
|
41
|
+
outputConfig: {
|
42
|
+
predictionsFormat: 'jsonl',
|
43
|
+
gcsDestination: {
|
44
|
+
outputUriPrefix: @gcs_output_uri
|
45
|
+
}
|
46
|
+
}
|
47
|
+
}.to_json
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Llmclt
|
4
|
+
module Request
|
5
|
+
class NonStreaming < Base
|
6
|
+
include Llmclt::Request::Chat
|
7
|
+
|
8
|
+
def endpoint_uri
|
9
|
+
URI.parse(
|
10
|
+
"https://#{endpoint_host}/v1/projects/#{@config.project_id}/locations/#{@config.location_id}/publishers/google/models/#{@config.model}:generateContent"
|
11
|
+
)
|
12
|
+
end
|
13
|
+
|
14
|
+
def build_response(response)
|
15
|
+
Llmclt::Response::NonStreaming.new(response)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Llmclt
|
4
|
+
module Request
|
5
|
+
class Streaming < Base
|
6
|
+
include Llmclt::Request::Chat
|
7
|
+
|
8
|
+
def endpoint_uri
|
9
|
+
URI.parse(
|
10
|
+
"https://#{endpoint_host}/v1/projects/#{@config.project_id}/locations/#{@config.location_id}/publishers/google/models/#{@config.model}:streamGenerateContent"
|
11
|
+
)
|
12
|
+
end
|
13
|
+
|
14
|
+
def build_response(response)
|
15
|
+
Llmclt::Response::Streaming.new(response)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Llmclt
|
4
|
+
module Response
|
5
|
+
class Base
|
6
|
+
attr_reader :response_orig, :response
|
7
|
+
|
8
|
+
def initialize(response)
|
9
|
+
@response_orig = response
|
10
|
+
@response = JSON.parse(response.body) if response.body
|
11
|
+
rescue JSON::ParserError
|
12
|
+
@response = nil
|
13
|
+
end
|
14
|
+
|
15
|
+
def status
|
16
|
+
response_orig.code.to_i
|
17
|
+
end
|
18
|
+
|
19
|
+
def success?
|
20
|
+
status == 200
|
21
|
+
end
|
22
|
+
|
23
|
+
def error?
|
24
|
+
status.between?(400, 599)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Llmclt
|
4
|
+
module Response
|
5
|
+
class Batch < Base
|
6
|
+
def batch_job_id
|
7
|
+
@response['name'].split('/').last
|
8
|
+
end
|
9
|
+
|
10
|
+
def state
|
11
|
+
@response['state']
|
12
|
+
end
|
13
|
+
|
14
|
+
def state_succeeded?
|
15
|
+
@response['state'] == 'JOB_STATE_SUCCEEDED'
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Llmclt
|
4
|
+
module Response
|
5
|
+
class NonStreaming < Base
|
6
|
+
include Llmclt::Response::Chat
|
7
|
+
|
8
|
+
def texts
|
9
|
+
candidates = response['candidates']
|
10
|
+
parts = candidates.map { |candidate| candidate['content']['parts'] }.flatten
|
11
|
+
parts.map { |part| part['text'] }
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Llmclt
|
4
|
+
module Response
|
5
|
+
class Streaming < Base
|
6
|
+
include Llmclt::Response::Chat
|
7
|
+
|
8
|
+
def texts
|
9
|
+
candidates = response.map { |res| res['candidates'] }.flatten
|
10
|
+
parts = candidates.map { |candidate| candidate['content']['parts'] }.flatten
|
11
|
+
parts.map { |part| part['text'] }
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Llmclt
|
4
|
+
module Syntax
|
5
|
+
class Chat
|
6
|
+
attr_reader :prompts, :config, :histories
|
7
|
+
attr_reader :params
|
8
|
+
|
9
|
+
def initialize(prompts, config, histories: [])
|
10
|
+
@prompts = Array(prompts)
|
11
|
+
@config = config
|
12
|
+
@histories = histories
|
13
|
+
@params = build_params
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_json(*_args)
|
17
|
+
params.size == 1 ? params.first.to_json : params.to_json
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_jsonl
|
21
|
+
params.map { |json| { request: json } }.map(&:to_json).join("\n")
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def build_params
|
27
|
+
prompts.each_with_object([]) do |prompt, prms|
|
28
|
+
prm = {}
|
29
|
+
prm.merge!(build_contents(prompt))
|
30
|
+
prm.merge!(build_system_instruction)
|
31
|
+
prms << prm
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def build_contents(prompt)
|
36
|
+
content = { contents: [] }
|
37
|
+
content.merge!(config.safety_config.build_request_content)
|
38
|
+
content.merge!(config.generation_config.build_request_content)
|
39
|
+
content[:contents].concat(build_history_contents(histories))
|
40
|
+
content[:contents] << build_user_prompt(prompt)
|
41
|
+
content
|
42
|
+
end
|
43
|
+
|
44
|
+
def build_history_contents(histories)
|
45
|
+
histories.map do |history|
|
46
|
+
{
|
47
|
+
role: history[:role],
|
48
|
+
parts: [{ text: history[:text] }]
|
49
|
+
}
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def build_user_prompt(prompt)
|
54
|
+
{
|
55
|
+
role: 'user',
|
56
|
+
parts: [
|
57
|
+
{
|
58
|
+
text: prompt
|
59
|
+
}
|
60
|
+
]
|
61
|
+
}
|
62
|
+
end
|
63
|
+
|
64
|
+
def build_system_instruction
|
65
|
+
return {} if config.system_instruction_prompt.nil? || config.system_instruction_prompt.empty?
|
66
|
+
|
67
|
+
{
|
68
|
+
systemInstruction: {
|
69
|
+
parts: [
|
70
|
+
{
|
71
|
+
text: config.system_instruction_prompt
|
72
|
+
}
|
73
|
+
]
|
74
|
+
}
|
75
|
+
}
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
data/lib/llmclt/version.rb
CHANGED
data/lib/llmclt.rb
CHANGED
@@ -7,10 +7,20 @@ require_relative 'llmclt/config'
|
|
7
7
|
require_relative 'llmclt/config/safety_config'
|
8
8
|
require_relative 'llmclt/config/generation_config'
|
9
9
|
require_relative 'llmclt/client'
|
10
|
-
require_relative 'llmclt/
|
10
|
+
require_relative 'llmclt/fetcher'
|
11
|
+
require_relative 'llmclt/syntax/chat'
|
12
|
+
require_relative 'llmclt/request/base'
|
13
|
+
require_relative 'llmclt/request/chat'
|
14
|
+
require_relative 'llmclt/request/non_streaming'
|
15
|
+
require_relative 'llmclt/request/streaming'
|
16
|
+
require_relative 'llmclt/request/batch'
|
11
17
|
require_relative 'llmclt/http/connection'
|
12
18
|
require_relative 'llmclt/http/connection_pool'
|
13
|
-
require_relative 'llmclt/response'
|
19
|
+
require_relative 'llmclt/response/base'
|
20
|
+
require_relative 'llmclt/response/chat'
|
21
|
+
require_relative 'llmclt/response/non_streaming'
|
22
|
+
require_relative 'llmclt/response/streaming'
|
23
|
+
require_relative 'llmclt/response/batch'
|
14
24
|
require_relative 'llmclt/version'
|
15
25
|
|
16
26
|
module Llmclt
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: llmclt
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- MasatoMiyoshi
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-
|
11
|
+
date: 2025-05-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -58,10 +58,20 @@ files:
|
|
58
58
|
- lib/llmclt/config.rb
|
59
59
|
- lib/llmclt/config/generation_config.rb
|
60
60
|
- lib/llmclt/config/safety_config.rb
|
61
|
+
- lib/llmclt/fetcher.rb
|
61
62
|
- lib/llmclt/http/connection.rb
|
62
63
|
- lib/llmclt/http/connection_pool.rb
|
63
|
-
- lib/llmclt/request.rb
|
64
|
-
- lib/llmclt/
|
64
|
+
- lib/llmclt/request/base.rb
|
65
|
+
- lib/llmclt/request/batch.rb
|
66
|
+
- lib/llmclt/request/chat.rb
|
67
|
+
- lib/llmclt/request/non_streaming.rb
|
68
|
+
- lib/llmclt/request/streaming.rb
|
69
|
+
- lib/llmclt/response/base.rb
|
70
|
+
- lib/llmclt/response/batch.rb
|
71
|
+
- lib/llmclt/response/chat.rb
|
72
|
+
- lib/llmclt/response/non_streaming.rb
|
73
|
+
- lib/llmclt/response/streaming.rb
|
74
|
+
- lib/llmclt/syntax/chat.rb
|
65
75
|
- lib/llmclt/version.rb
|
66
76
|
- sig/llmclt.rbs
|
67
77
|
homepage: https://github.com/MasatoMiyoshi/llmclt
|
@@ -79,7 +89,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
79
89
|
requirements:
|
80
90
|
- - ">="
|
81
91
|
- !ruby/object:Gem::Version
|
82
|
-
version:
|
92
|
+
version: 3.1.0
|
83
93
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
84
94
|
requirements:
|
85
95
|
- - ">="
|
data/lib/llmclt/request.rb
DELETED
@@ -1,128 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Llmclt
|
4
|
-
class Request
|
5
|
-
def initialize(config)
|
6
|
-
@config = config
|
7
|
-
@pool = Llmclt::Http::ConnectionPool.new
|
8
|
-
end
|
9
|
-
|
10
|
-
def run(prompt, stream: false, histories: [])
|
11
|
-
uri = request_uri(stream)
|
12
|
-
request = build_request(uri, prompt, histories)
|
13
|
-
http = checkout_http(uri)
|
14
|
-
response = http.start { |h| h.request(request) }
|
15
|
-
Llmclt::Response.new(response)
|
16
|
-
end
|
17
|
-
|
18
|
-
def shutdown
|
19
|
-
@pool.shutdown
|
20
|
-
end
|
21
|
-
|
22
|
-
private
|
23
|
-
|
24
|
-
def request_uri(stream)
|
25
|
-
if stream
|
26
|
-
URI.parse("#{endpoint_url}:streamGenerateContent")
|
27
|
-
else
|
28
|
-
URI.parse("#{endpoint_url}:generateContent")
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
def endpoint_url
|
33
|
-
"https://#{@config.location_id}-aiplatform.googleapis.com/v1/projects/#{@config.project_id}/locations/#{@config.location_id}/publishers/google/models/#{@config.model}"
|
34
|
-
end
|
35
|
-
|
36
|
-
def request_headers
|
37
|
-
authorizer.apply({ 'Content-Type': 'application/json' })
|
38
|
-
end
|
39
|
-
|
40
|
-
def authorizer
|
41
|
-
if @authorizer.nil? || @authorizer.expires_within?(60)
|
42
|
-
@authorizer = Google::Auth::ServiceAccountCredentials.make_creds(
|
43
|
-
json_key_io: StringIO.new(@config.service_account_json),
|
44
|
-
scope: 'https://www.googleapis.com/auth/cloud-platform'
|
45
|
-
)
|
46
|
-
@authorizer.fetch_access_token!
|
47
|
-
end
|
48
|
-
@authorizer
|
49
|
-
end
|
50
|
-
|
51
|
-
def checkout_http(uri)
|
52
|
-
@pool.checkout(pool_name(uri)) { build_http(uri) }
|
53
|
-
end
|
54
|
-
|
55
|
-
def pool_name(uri)
|
56
|
-
"#{uri.scheme}_#{uri.host}_#{uri.port || uri.default_port}"
|
57
|
-
end
|
58
|
-
|
59
|
-
def build_http(uri)
|
60
|
-
http = Net::HTTP.new(uri.host, uri.port)
|
61
|
-
http.open_timeout = @config.open_timeout
|
62
|
-
http.read_timeout = @config.read_timeout
|
63
|
-
http.keep_alive_timeout = @config.keep_alive_timeout
|
64
|
-
http.use_ssl = true
|
65
|
-
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
66
|
-
http
|
67
|
-
end
|
68
|
-
|
69
|
-
def build_request(uri, prompt, histories)
|
70
|
-
request = Net::HTTP::Post.new(uri.request_uri)
|
71
|
-
request_headers.each do |key, value|
|
72
|
-
request[key] = value
|
73
|
-
end
|
74
|
-
request.body = build_request_json(prompt, histories)
|
75
|
-
request
|
76
|
-
end
|
77
|
-
|
78
|
-
def build_request_json(prompt, histories)
|
79
|
-
req = {}
|
80
|
-
req.merge!(build_request_contents(prompt, histories))
|
81
|
-
req.merge!(build_request_system_instruction)
|
82
|
-
req.to_json
|
83
|
-
end
|
84
|
-
|
85
|
-
def build_request_contents(prompt, histories)
|
86
|
-
content = { contents: [] }
|
87
|
-
content.merge!(@config.safety_config.build_request_content)
|
88
|
-
content.merge!(@config.generation_config.build_request_content)
|
89
|
-
content[:contents].concat(build_request_history_contents(histories))
|
90
|
-
content[:contents] << build_request_user_prompt(prompt)
|
91
|
-
content
|
92
|
-
end
|
93
|
-
|
94
|
-
def build_request_history_contents(histories)
|
95
|
-
histories.map do |history|
|
96
|
-
{
|
97
|
-
role: history[:role],
|
98
|
-
parts: [{ text: history[:text] }]
|
99
|
-
}
|
100
|
-
end
|
101
|
-
end
|
102
|
-
|
103
|
-
def build_request_user_prompt(prompt)
|
104
|
-
{
|
105
|
-
role: 'user',
|
106
|
-
parts: [
|
107
|
-
{
|
108
|
-
text: prompt
|
109
|
-
}
|
110
|
-
]
|
111
|
-
}
|
112
|
-
end
|
113
|
-
|
114
|
-
def build_request_system_instruction
|
115
|
-
return {} if @config.system_instruction_prompt.nil? || @config.system_instruction_prompt.empty?
|
116
|
-
|
117
|
-
{
|
118
|
-
systemInstruction: {
|
119
|
-
parts: [
|
120
|
-
{
|
121
|
-
text: @config.system_instruction_prompt
|
122
|
-
}
|
123
|
-
]
|
124
|
-
}
|
125
|
-
}
|
126
|
-
end
|
127
|
-
end
|
128
|
-
end
|
data/lib/llmclt/response.rb
DELETED
@@ -1,46 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Llmclt
|
4
|
-
class Response
|
5
|
-
attr_reader :response_orig, :response
|
6
|
-
|
7
|
-
def initialize(response)
|
8
|
-
@response_orig = response
|
9
|
-
@response = JSON.parse(response.body) if response.body
|
10
|
-
rescue JSON::ParserError
|
11
|
-
@response = nil
|
12
|
-
end
|
13
|
-
|
14
|
-
def status
|
15
|
-
response_orig.code.to_i
|
16
|
-
end
|
17
|
-
|
18
|
-
def success?
|
19
|
-
status == 200
|
20
|
-
end
|
21
|
-
|
22
|
-
def error?
|
23
|
-
status >= 400 && status <= 599
|
24
|
-
end
|
25
|
-
|
26
|
-
def text
|
27
|
-
texts.join
|
28
|
-
end
|
29
|
-
|
30
|
-
def texts
|
31
|
-
candidates = responses.map { |res| res['candidates'] }.flatten
|
32
|
-
parts = candidates.map { |candidate| candidate['content']['parts'] }.flatten
|
33
|
-
parts.map { |part| part['text'] }
|
34
|
-
end
|
35
|
-
|
36
|
-
def stream?
|
37
|
-
response.is_a?(Array)
|
38
|
-
end
|
39
|
-
|
40
|
-
private
|
41
|
-
|
42
|
-
def responses
|
43
|
-
stream? ? response : [response]
|
44
|
-
end
|
45
|
-
end
|
46
|
-
end
|