google-genai 0.1.0
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 +7 -0
- data/LICENSE +202 -0
- data/README.md +39 -0
- data/lib/google/genai/api_client.rb +116 -0
- data/lib/google/genai/batches.rb +71 -0
- data/lib/google/genai/caches.rb +55 -0
- data/lib/google/genai/chats.rb +37 -0
- data/lib/google/genai/client.rb +55 -0
- data/lib/google/genai/errors.rb +43 -0
- data/lib/google/genai/files.rb +85 -0
- data/lib/google/genai/live.rb +80 -0
- data/lib/google/genai/live_music.rb +98 -0
- data/lib/google/genai/models.rb +69 -0
- data/lib/google/genai/operations.rb +18 -0
- data/lib/google/genai/pagers.rb +57 -0
- data/lib/google/genai/tokens.rb +40 -0
- data/lib/google/genai/tunings.rb +64 -0
- data/lib/google/genai/types.rb +103 -0
- data/lib/google/genai/version.rb +7 -0
- data/lib/google/genai.rb +24 -0
- metadata +179 -0
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'pagers'
|
|
4
|
+
|
|
5
|
+
module Google
|
|
6
|
+
module Genai
|
|
7
|
+
class Files
|
|
8
|
+
def initialize(api_client)
|
|
9
|
+
@api_client = api_client
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def upload(file:, config: nil)
|
|
13
|
+
file_path = file.is_a?(String) ? file : file.path
|
|
14
|
+
raise ArgumentError, "File not found: #{file_path}" unless File.exist?(file_path)
|
|
15
|
+
|
|
16
|
+
config ||= {}
|
|
17
|
+
mime_type = config[:mime_type] || MimeMagic.by_path(file_path)&.type
|
|
18
|
+
raise ArgumentError, "Could not determine MIME type for file: #{file_path}" unless mime_type
|
|
19
|
+
|
|
20
|
+
display_name = config[:display_name] || File.basename(file_path)
|
|
21
|
+
file_size = File.size(file_path)
|
|
22
|
+
|
|
23
|
+
response_data = @api_client.upload_file(file_path, file_size, mime_type, display_name: display_name)
|
|
24
|
+
file_info = Types::File.new(response_data['file'])
|
|
25
|
+
|
|
26
|
+
# Poll for file processing to complete with exponential backoff
|
|
27
|
+
timeout = 120 # 2 minutes
|
|
28
|
+
start_time = Time.now
|
|
29
|
+
delay = 0.1 # Initial delay of 100ms
|
|
30
|
+
|
|
31
|
+
loop do
|
|
32
|
+
file_info = get(name: file_info.name)
|
|
33
|
+
case file_info.state
|
|
34
|
+
when 'ACTIVE'
|
|
35
|
+
return file_info
|
|
36
|
+
when 'FAILED'
|
|
37
|
+
raise "File processing failed: #{file_info.error}"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
raise "File processing timed out" if Time.now - start_time > timeout
|
|
41
|
+
|
|
42
|
+
sleep delay
|
|
43
|
+
delay = [delay * 1.5, 5].min # Increase delay, but cap at 5 seconds
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def get(name:, config: nil)
|
|
48
|
+
file_id = name.start_with?('files/') ? name.split('/').last : name
|
|
49
|
+
response = @api_client.get("v1beta/files/#{file_id}")
|
|
50
|
+
Types::File.new(JSON.parse(response.body))
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def delete(name:, config: nil)
|
|
54
|
+
file_id = name.start_with?('files/') ? name.split('/').last : name
|
|
55
|
+
@api_client.delete("v1beta/files/#{file_id}")
|
|
56
|
+
nil
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def list(config: nil)
|
|
60
|
+
list_request = ->(options) do
|
|
61
|
+
path = "v1beta/files"
|
|
62
|
+
params = {}
|
|
63
|
+
params[:pageToken] = options[:page_token] if options&.key?(:page_token)
|
|
64
|
+
params[:pageSize] = options[:page_size] if options&.key?(:page_size)
|
|
65
|
+
path += "?#{URI.encode_www_form(params)}" unless params.empty?
|
|
66
|
+
@api_client.get(path)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
response = list_request.call(config)
|
|
70
|
+
|
|
71
|
+
Pager.new(
|
|
72
|
+
name: :files,
|
|
73
|
+
request: list_request,
|
|
74
|
+
response: response,
|
|
75
|
+
config: config,
|
|
76
|
+
item_class: Types::File
|
|
77
|
+
)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def download(file:, config: nil)
|
|
81
|
+
# TODO: Implement file download logic
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'websocket-client-simple'
|
|
4
|
+
require 'json'
|
|
5
|
+
require_relative 'live_music'
|
|
6
|
+
|
|
7
|
+
module Google
|
|
8
|
+
module Genai
|
|
9
|
+
class Live
|
|
10
|
+
def initialize(api_client)
|
|
11
|
+
@api_client = api_client
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def music
|
|
15
|
+
@music ||= LiveMusic.new(@api_client)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def connect(model:, config: nil)
|
|
19
|
+
raise "Live API is not supported for Vertex AI yet" if @api_client.vertexai
|
|
20
|
+
|
|
21
|
+
base_url = "wss://generativelanguage.googleapis.com"
|
|
22
|
+
version = @api_client.instance_variable_get(:@http_options)&.[](:api_version) || 'v1beta'
|
|
23
|
+
api_key = @api_client.api_key
|
|
24
|
+
|
|
25
|
+
uri = "#{base_url}/ws/google.ai.generativelanguage.#{version}.GenerativeService.BidiGenerateContent?key=#{api_key}"
|
|
26
|
+
|
|
27
|
+
ws = WebSocket::Client::Simple.connect(uri)
|
|
28
|
+
|
|
29
|
+
session = Session.new(ws)
|
|
30
|
+
|
|
31
|
+
setup_message = {
|
|
32
|
+
setup: {
|
|
33
|
+
model: "models/#{model}",
|
|
34
|
+
generationConfig: config
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
ws.send(setup_message.to_json)
|
|
38
|
+
|
|
39
|
+
# Block until the connection is open and initial setup is confirmed.
|
|
40
|
+
# This is a simplified way to handle the async nature of websockets in a sync method.
|
|
41
|
+
# A more robust solution would involve a proper event loop.
|
|
42
|
+
sleep 0.1 until ws.open?
|
|
43
|
+
|
|
44
|
+
yield session
|
|
45
|
+
ensure
|
|
46
|
+
ws.close if ws&.open?
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
class Session
|
|
51
|
+
def initialize(websocket)
|
|
52
|
+
@ws = websocket
|
|
53
|
+
@message_queue = Queue.new
|
|
54
|
+
|
|
55
|
+
@ws.on :message do |msg|
|
|
56
|
+
@message_queue.push(JSON.parse(msg.data))
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
@ws.on :error do |err|
|
|
60
|
+
# For now, just print the error. A more robust error handling can be added.
|
|
61
|
+
puts "WebSocket Error: #{err.message}"
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def send_client_content(turns:, turn_complete: true)
|
|
66
|
+
message = {
|
|
67
|
+
clientContent: {
|
|
68
|
+
turns: turns,
|
|
69
|
+
turnComplete: turn_complete
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
@ws.send(message.to_json)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def receive
|
|
76
|
+
@message_queue.pop
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'websocket-client-simple'
|
|
4
|
+
require 'json'
|
|
5
|
+
|
|
6
|
+
module Google
|
|
7
|
+
module Genai
|
|
8
|
+
class LiveMusic
|
|
9
|
+
def initialize(api_client)
|
|
10
|
+
@api_client = api_client
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def connect(model:)
|
|
14
|
+
raise "Live Music API is not supported for Vertex AI yet" if @api_client.vertexai
|
|
15
|
+
|
|
16
|
+
base_url = "wss://generativelanguage.googleapis.com"
|
|
17
|
+
version = @api_client.instance_variable_get(:@http_options)&.[](:api_version) || 'v1beta'
|
|
18
|
+
api_key = @api_client.api_key
|
|
19
|
+
|
|
20
|
+
uri = "#{base_url}/ws/google.ai.generativelanguage.#{version}.GenerativeService.BidiGenerateMusic?key=#{api_key}"
|
|
21
|
+
|
|
22
|
+
ws = WebSocket::Client::Simple.connect(uri)
|
|
23
|
+
|
|
24
|
+
session = MusicSession.new(ws)
|
|
25
|
+
|
|
26
|
+
setup_message = {
|
|
27
|
+
setup: {
|
|
28
|
+
model: "models/#{model}"
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
ws.send(setup_message.to_json)
|
|
32
|
+
|
|
33
|
+
sleep 0.1 until ws.open?
|
|
34
|
+
|
|
35
|
+
yield session
|
|
36
|
+
ensure
|
|
37
|
+
ws.close if ws&.open?
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
class MusicSession
|
|
42
|
+
def initialize(websocket)
|
|
43
|
+
@ws = websocket
|
|
44
|
+
@message_queue = Queue.new
|
|
45
|
+
|
|
46
|
+
@ws.on :message do |msg|
|
|
47
|
+
@message_queue.push(JSON.parse(msg.data))
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
@ws.on :error do |err|
|
|
51
|
+
puts "WebSocket Error: #{err.message}"
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def set_weighted_prompts(prompts:)
|
|
56
|
+
message = {
|
|
57
|
+
clientContent: {
|
|
58
|
+
weightedPrompts: prompts.map(&:to_h)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
@ws.send(message.to_json)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def set_music_generation_config(config:)
|
|
65
|
+
@ws.send({ musicGenerationConfig: config }.to_json)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def play
|
|
69
|
+
_send_control_signal('PLAY')
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def pause
|
|
73
|
+
_send_control_signal('PAUSE')
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def stop
|
|
77
|
+
_send_control_signal('STOP')
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def reset_context
|
|
81
|
+
_send_control_signal('RESET_CONTEXT')
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def receive
|
|
85
|
+
@message_queue.pop
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
private
|
|
89
|
+
|
|
90
|
+
def _send_control_signal(control_signal)
|
|
91
|
+
message = {
|
|
92
|
+
playbackControl: control_signal
|
|
93
|
+
}
|
|
94
|
+
@ws.send(message.to_json)
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'types'
|
|
4
|
+
require_relative 'pagers'
|
|
5
|
+
|
|
6
|
+
module Google
|
|
7
|
+
module Genai
|
|
8
|
+
class Models
|
|
9
|
+
def initialize(api_client)
|
|
10
|
+
@api_client = api_client
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def generate_content(model:, contents:, config: nil)
|
|
14
|
+
path = "v1beta/models/#{model}:generateContent"
|
|
15
|
+
body = {
|
|
16
|
+
contents: normalize_contents(contents)
|
|
17
|
+
}
|
|
18
|
+
body[:generationConfig] = config if config
|
|
19
|
+
|
|
20
|
+
response = @api_client.request(:post, path, body: body)
|
|
21
|
+
Types::GenerateContentResponse.new(JSON.parse(response.body))
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def list(config: nil)
|
|
25
|
+
query_base = config&.dig(:query_base) != false
|
|
26
|
+
|
|
27
|
+
list_request = ->(options) do
|
|
28
|
+
path = "v1beta/#{query_base ? 'models' : 'tunedModels'}"
|
|
29
|
+
params = {}
|
|
30
|
+
params[:pageToken] = options[:page_token] if options&.key?(:page_token)
|
|
31
|
+
params[:pageSize] = options[:page_size] if options&.key?(:page_size)
|
|
32
|
+
path += "?#{URI.encode_www_form(params)}" unless params.empty?
|
|
33
|
+
@api_client.get(path)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
response = list_request.call(config)
|
|
37
|
+
|
|
38
|
+
Pager.new(
|
|
39
|
+
name: query_base ? :models : :tunedModels,
|
|
40
|
+
request: list_request,
|
|
41
|
+
response: response,
|
|
42
|
+
config: config,
|
|
43
|
+
item_class: Types::Model
|
|
44
|
+
)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
def normalize_contents(contents)
|
|
50
|
+
contents = [contents] unless contents.is_a?(Array)
|
|
51
|
+
|
|
52
|
+
contents.map do |item|
|
|
53
|
+
case item
|
|
54
|
+
when String
|
|
55
|
+
{ role: 'user', parts: [{ text: item }] }
|
|
56
|
+
when Hash
|
|
57
|
+
item # Assumes it's already in the correct format
|
|
58
|
+
when Types::Content
|
|
59
|
+
item.to_h
|
|
60
|
+
when Types::File
|
|
61
|
+
{ role: 'user', parts: [{ file_data: { mime_type: item.mime_type, file_uri: item.uri } }] }
|
|
62
|
+
else
|
|
63
|
+
raise ArgumentError, "Unsupported content type: #{item.class}"
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'types'
|
|
4
|
+
|
|
5
|
+
module Google
|
|
6
|
+
module Genai
|
|
7
|
+
class Operations
|
|
8
|
+
def initialize(api_client)
|
|
9
|
+
@api_client = api_client
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def get(name:, config: nil)
|
|
13
|
+
response = @api_client.get("v1beta/#{name}")
|
|
14
|
+
Types::Operation.new(JSON.parse(response.body))
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Google
|
|
4
|
+
module Genai
|
|
5
|
+
class Pager
|
|
6
|
+
include Enumerable
|
|
7
|
+
|
|
8
|
+
attr_reader :name, :page_size, :config, :sdk_http_response
|
|
9
|
+
|
|
10
|
+
def initialize(name:, request:, response:, config:, item_class:)
|
|
11
|
+
@item_class = item_class
|
|
12
|
+
_init_page(name: name, request: request, response: response, config: config)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def each(&block)
|
|
16
|
+
loop do
|
|
17
|
+
@page.each(&block)
|
|
18
|
+
break unless next_page_token
|
|
19
|
+
next_page
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def page
|
|
24
|
+
@page
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def next_page
|
|
28
|
+
raise IndexError, 'No more pages to fetch.' unless next_page_token
|
|
29
|
+
|
|
30
|
+
response = @request.call(config: @config)
|
|
31
|
+
_init_page(name: @name, request: @request, response: response, config: @config)
|
|
32
|
+
@page
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def _init_page(name:, request:, response:, config:)
|
|
38
|
+
@name = name
|
|
39
|
+
@request = request
|
|
40
|
+
|
|
41
|
+
response_body = JSON.parse(response.body)
|
|
42
|
+
@page = (response_body[name.to_s] || []).map { |item_data| @item_class.new(item_data) }
|
|
43
|
+
|
|
44
|
+
@sdk_http_response = response
|
|
45
|
+
|
|
46
|
+
@config = config ? config.dup : {}
|
|
47
|
+
@config[:page_token] = response_body['nextPageToken']
|
|
48
|
+
|
|
49
|
+
@page_size = @config[:page_size] || @page.length
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def next_page_token
|
|
53
|
+
@config[:page_token]
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'types'
|
|
4
|
+
|
|
5
|
+
module Google
|
|
6
|
+
module Genai
|
|
7
|
+
class Tokens
|
|
8
|
+
def initialize(api_client)
|
|
9
|
+
@api_client = api_client
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def create(config: nil)
|
|
13
|
+
raise "Tokens is only supported for Gemini API" if @api_client.vertexai
|
|
14
|
+
|
|
15
|
+
# This is a simplified port of the Python SDK's logic.
|
|
16
|
+
# The full logic for field masks is complex and will be implemented later.
|
|
17
|
+
|
|
18
|
+
body = {}
|
|
19
|
+
body[:expireTime] = config[:expire_time] if config&.key?(:expire_time)
|
|
20
|
+
body[:uses] = config[:uses] if config&.key?(:uses)
|
|
21
|
+
|
|
22
|
+
if config&.key?(:live_connect_constraints)
|
|
23
|
+
# This part of the conversion is complex and will require more detailed mapping.
|
|
24
|
+
# For now, we'll pass a simplified version.
|
|
25
|
+
body[:bidiGenerateContentSetup] = {
|
|
26
|
+
setup: {
|
|
27
|
+
model: config[:live_connect_constraints][:model]
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
if config[:live_connect_constraints][:config]
|
|
31
|
+
body[:bidiGenerateContentSetup][:setup][:generationConfig] = config[:live_connect_constraints][:config]
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
response = @api_client.request(:post, "v1alpha/authTokens", body: body)
|
|
36
|
+
Types::AuthToken.new(JSON.parse(response.body))
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'types'
|
|
4
|
+
require_relative 'pagers'
|
|
5
|
+
|
|
6
|
+
module Google
|
|
7
|
+
module Genai
|
|
8
|
+
class Tunings
|
|
9
|
+
def initialize(api_client)
|
|
10
|
+
@api_client = api_client
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def create(base_model:, training_dataset:, config: nil)
|
|
14
|
+
raise "Tuning is only supported for Vertex AI" unless @api_client.vertexai
|
|
15
|
+
|
|
16
|
+
body = {
|
|
17
|
+
baseModel: base_model,
|
|
18
|
+
supervisedTuningSpec: {
|
|
19
|
+
trainingDatasetUri: training_dataset[:gcs_uri] || training_dataset[:vertex_dataset_resource]
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
# TODO: Add other config options
|
|
23
|
+
|
|
24
|
+
response = @api_client.request(:post, "v1beta/tuningJobs", body: body)
|
|
25
|
+
Types::TuningJob.new(JSON.parse(response.body))
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def get(name:, config: nil)
|
|
29
|
+
raise "Tuning is only supported for Vertex AI" unless @api_client.vertexai
|
|
30
|
+
response = @api_client.get("v1beta/#{name}")
|
|
31
|
+
Types::TuningJob.new(JSON.parse(response.body))
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def list(config: nil)
|
|
35
|
+
raise "Tuning is only supported for Vertex AI" unless @api_client.vertexai
|
|
36
|
+
|
|
37
|
+
list_request = ->(options) do
|
|
38
|
+
path = "v1beta/tuningJobs"
|
|
39
|
+
params = {}
|
|
40
|
+
params[:pageToken] = options[:page_token] if options&.key?(:page_token)
|
|
41
|
+
params[:pageSize] = options[:page_size] if options&.key?(:page_size)
|
|
42
|
+
path += "?#{URI.encode_www_form(params)}" unless params.empty?
|
|
43
|
+
@api_client.get(path)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
response = list_request.call(config)
|
|
47
|
+
|
|
48
|
+
Pager.new(
|
|
49
|
+
name: :tuningJobs,
|
|
50
|
+
request: list_request,
|
|
51
|
+
response: response,
|
|
52
|
+
config: config,
|
|
53
|
+
item_class: Types::TuningJob
|
|
54
|
+
)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def cancel(name:, config: nil)
|
|
58
|
+
raise "Tuning is only supported for Vertex AI" unless @api_client.vertexai
|
|
59
|
+
@api_client.request(:post, "v1beta/#{name}:cancel", body: {})
|
|
60
|
+
nil
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require "base64"
|
|
3
|
+
|
|
4
|
+
module Google
|
|
5
|
+
module Genai
|
|
6
|
+
module Types
|
|
7
|
+
class Base
|
|
8
|
+
def initialize(attributes = {})
|
|
9
|
+
attributes.each do |key, value|
|
|
10
|
+
setter = "#{key}="
|
|
11
|
+
public_send(setter, value) if respond_to?(setter)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
class Blob < Base
|
|
17
|
+
attr_accessor :mime_type, :data
|
|
18
|
+
|
|
19
|
+
def to_h
|
|
20
|
+
{ mimeType: mime_type, data: Base64.strict_encode64(data) }
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
class FileData < Base
|
|
25
|
+
attr_accessor :mime_type, :file_uri
|
|
26
|
+
|
|
27
|
+
def to_h
|
|
28
|
+
{ mimeType: mime_type, fileUri: file_uri }
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
class Part < Base
|
|
33
|
+
attr_accessor :text, :inline_data, :file_data
|
|
34
|
+
|
|
35
|
+
def to_h
|
|
36
|
+
data = {}
|
|
37
|
+
data[:text] = text if text
|
|
38
|
+
data[:inlineData] = inline_data.to_h if inline_data
|
|
39
|
+
data[:fileData] = file_data.to_h if file_data
|
|
40
|
+
data
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
class Content < Base
|
|
45
|
+
attr_accessor :role, :parts
|
|
46
|
+
|
|
47
|
+
def initialize(attributes = {})
|
|
48
|
+
super
|
|
49
|
+
self.parts = Array(self.parts).map { |p| p.is_a?(Part) ? p : Part.new(p) }
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def to_h
|
|
53
|
+
{
|
|
54
|
+
role: role,
|
|
55
|
+
parts: parts.map(&:to_h)
|
|
56
|
+
}
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
class GenerateContentResponse < Base
|
|
61
|
+
attr_accessor :candidates
|
|
62
|
+
|
|
63
|
+
def initialize(attributes = {})
|
|
64
|
+
super
|
|
65
|
+
self.candidates = Array(self.candidates).map { |c| c.is_a?(Candidate) ? c : Candidate.new(c) }
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def text
|
|
69
|
+
candidates&.first&.content&.parts&.map(&:text)&.join
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
class Candidate < Base
|
|
74
|
+
attr_accessor :content, :finish_reason, :safety_ratings
|
|
75
|
+
|
|
76
|
+
def initialize(attributes = {})
|
|
77
|
+
super
|
|
78
|
+
self.content = Content.new(self.content) if self.content.is_a?(Hash)
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
class File < Base
|
|
83
|
+
attr_accessor :name, :display_name, :mime_type, :size_bytes, :create_time, :update_time, :expiration_time, :sha256_hash, :uri, :state, :error
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
class TuningJob < Base
|
|
87
|
+
attr_accessor :name, :state, :create_time, :end_time, :tuned_model
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
class CachedContent < Base
|
|
91
|
+
attr_accessor :name, :display_name, :model, :create_time, :update_time, :expire_time, :usage_metadata
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
class BatchJob < Base
|
|
95
|
+
attr_accessor :name, :model, :state, :create_time, :end_time, :update_time, :error
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
class Operation < Base
|
|
99
|
+
attr_accessor :name, :metadata, :done, :error, :response
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
data/lib/google/genai.rb
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "zeitwerk"
|
|
4
|
+
loader = Zeitwerk::Loader.for_gem
|
|
5
|
+
loader.setup
|
|
6
|
+
|
|
7
|
+
require_relative "genai/version"
|
|
8
|
+
require_relative "genai/api_client"
|
|
9
|
+
require_relative "genai/models"
|
|
10
|
+
require_relative "genai/chats"
|
|
11
|
+
require_relative "genai/files"
|
|
12
|
+
require_relative "genai/tunings"
|
|
13
|
+
require_relative "genai/caches"
|
|
14
|
+
require_relative "genai/batches"
|
|
15
|
+
require_relative "genai/operations"
|
|
16
|
+
require_relative "genai/tokens"
|
|
17
|
+
require_relative "genai/live"
|
|
18
|
+
require_relative "genai/client"
|
|
19
|
+
|
|
20
|
+
module Google
|
|
21
|
+
module Genai
|
|
22
|
+
# Your code goes here...
|
|
23
|
+
end
|
|
24
|
+
end
|