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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 664d39ff19d67ce78c661511aee009690e3f14c0654d6e1cca5c556a89cce060
4
- data.tar.gz: 5536913892e091ad1b8435ee2a8ec508e96a9103ed707bd5eed05df742b61163
3
+ metadata.gz: 624b382b4d3337d9225a4c4d6d6892372f5a60444528dd2fc6a2f151eea81fae
4
+ data.tar.gz: 8a76cd4a7be9ec84f145e099cd3c0418428f23b4d58364422ff9319dc31d70ab
5
5
  SHA512:
6
- metadata.gz: 9af77e20a63480c33f585359acf21179371726c497e01ff47248da5877f7b9f68db78deccb4806a290333d17276920c2fdf22c4d817de8a249159b501e770509
7
- data.tar.gz: ad2524e8d9735e0a7b1249c0c93c380b6b6151eb9f22c99be9541256a937962a60cd17b7f70e75afc1c822eeb818a18aa1ee8333323d1494e3c53f1156860c3f
6
+ metadata.gz: 276766c1a32fce5c867b82575e49ef8fb361c8acf9ca06063c7adacc9aac877f1cbc5e75e351dc09da52754daea576972bedb75be5ec0a237e8b88e68e7c4b79
7
+ data.tar.gz: 7f2cc43c7cb87bd1f032031c562c47c8fed6be5b899a5e2eea6c72ac8804fce1330e13f871803e7565a0abeadb12734d3f98acbc90db5d32f339d4658845a03a
data/.rubocop.yml CHANGED
@@ -1,12 +1,14 @@
1
1
  require:
2
2
  - rubocop-rake
3
+
4
+ plugins:
3
5
  - rubocop-rspec
4
6
  - rubocop-performance
5
7
 
6
8
  inherit_from: .rubocop_todo.yml
7
9
 
8
10
  AllCops:
9
- TargetRubyVersion: 2.7
11
+ TargetRubyVersion: 3.1
10
12
  NewCops: enable
11
13
  Exclude:
12
14
  - 'log/**/*'
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
- @request = Llmclt::Request.new(@config)
7
+ @fetcher = Llmclt::Fetcher.new(@config)
8
8
  end
9
9
 
10
10
  def request(prompt, stream: false, histories: [])
11
- @request.run(prompt, stream: stream, histories: histories)
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
- @request.shutdown
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,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Llmclt
4
+ module Request
5
+ module Chat
6
+ private
7
+
8
+ def build_request_json
9
+ Llmclt::Syntax::Chat.new(@prompt, @config, histories: @histories).to_json
10
+ end
11
+ end
12
+ end
13
+ 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
+ module Chat
6
+ def text
7
+ texts.join
8
+ end
9
+
10
+ def texts
11
+ raise NotImplementedError, "#{class_name}#{__method__} is not implemented"
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 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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Llmclt
4
- VERSION = '1.0.1'
4
+ VERSION = '1.0.2'
5
5
  end
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/request'
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.1
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-01-28 00:00:00.000000000 Z
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/response.rb
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: 2.7.0
92
+ version: 3.1.0
83
93
  required_rubygems_version: !ruby/object:Gem::Requirement
84
94
  requirements:
85
95
  - - ">="
@@ -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
@@ -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