llm.rb 4.8.0 → 4.9.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 +4 -4
- data/README.md +337 -590
- data/data/anthropic.json +770 -0
- data/data/deepseek.json +75 -0
- data/data/google.json +1050 -0
- data/data/openai.json +1421 -0
- data/data/xai.json +792 -0
- data/data/zai.json +330 -0
- data/lib/llm/agent.rb +42 -41
- data/lib/llm/bot.rb +1 -263
- data/lib/llm/buffer.rb +7 -0
- data/lib/llm/{session → context}/deserializer.rb +4 -3
- data/lib/llm/context.rb +292 -0
- data/lib/llm/cost.rb +26 -0
- data/lib/llm/error.rb +8 -0
- data/lib/llm/function/array.rb +61 -0
- data/lib/llm/function/fiber_group.rb +91 -0
- data/lib/llm/function/task_group.rb +89 -0
- data/lib/llm/function/thread_group.rb +94 -0
- data/lib/llm/function.rb +75 -10
- data/lib/llm/mcp/command.rb +108 -0
- data/lib/llm/mcp/error.rb +31 -0
- data/lib/llm/mcp/pipe.rb +82 -0
- data/lib/llm/mcp/rpc.rb +118 -0
- data/lib/llm/mcp/transport/stdio.rb +85 -0
- data/lib/llm/mcp.rb +102 -0
- data/lib/llm/message.rb +13 -11
- data/lib/llm/model.rb +2 -2
- data/lib/llm/prompt.rb +17 -7
- data/lib/llm/provider.rb +32 -17
- data/lib/llm/providers/anthropic/files.rb +3 -3
- data/lib/llm/providers/anthropic.rb +19 -4
- data/lib/llm/providers/deepseek.rb +10 -3
- data/lib/llm/providers/{gemini → google}/audio.rb +6 -6
- data/lib/llm/providers/{gemini → google}/error_handler.rb +2 -2
- data/lib/llm/providers/{gemini → google}/files.rb +11 -11
- data/lib/llm/providers/{gemini → google}/images.rb +7 -7
- data/lib/llm/providers/{gemini → google}/models.rb +5 -5
- data/lib/llm/providers/{gemini → google}/request_adapter/completion.rb +7 -3
- data/lib/llm/providers/{gemini → google}/request_adapter.rb +1 -1
- data/lib/llm/providers/{gemini → google}/response_adapter/completion.rb +7 -7
- data/lib/llm/providers/{gemini → google}/response_adapter/embedding.rb +1 -1
- data/lib/llm/providers/{gemini → google}/response_adapter/file.rb +1 -1
- data/lib/llm/providers/{gemini → google}/response_adapter/files.rb +1 -1
- data/lib/llm/providers/{gemini → google}/response_adapter/image.rb +1 -1
- data/lib/llm/providers/{gemini → google}/response_adapter/models.rb +1 -1
- data/lib/llm/providers/{gemini → google}/response_adapter/web_search.rb +2 -2
- data/lib/llm/providers/{gemini → google}/response_adapter.rb +8 -8
- data/lib/llm/providers/{gemini → google}/stream_parser.rb +3 -3
- data/lib/llm/providers/{gemini.rb → google.rb} +41 -26
- data/lib/llm/providers/llamacpp.rb +10 -3
- data/lib/llm/providers/ollama.rb +19 -4
- data/lib/llm/providers/openai/files.rb +3 -3
- data/lib/llm/providers/openai/response_adapter/completion.rb +9 -1
- data/lib/llm/providers/openai/response_adapter/responds.rb +9 -1
- data/lib/llm/providers/openai/responses.rb +9 -1
- data/lib/llm/providers/openai/stream_parser.rb +2 -0
- data/lib/llm/providers/openai.rb +19 -4
- data/lib/llm/providers/xai.rb +10 -3
- data/lib/llm/providers/zai.rb +9 -2
- data/lib/llm/registry.rb +81 -0
- data/lib/llm/schema/parser.rb +109 -0
- data/lib/llm/schema.rb +4 -0
- data/lib/llm/server_tool.rb +5 -5
- data/lib/llm/session.rb +10 -1
- data/lib/llm/tool.rb +85 -4
- data/lib/llm/tracer/logger.rb +1 -1
- data/lib/llm/tracer/telemetry.rb +7 -7
- data/lib/llm/tracer.rb +3 -3
- data/lib/llm/usage.rb +5 -0
- data/lib/llm/version.rb +1 -1
- data/lib/llm.rb +39 -6
- data/llm.gemspec +45 -8
- metadata +81 -28
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
class LLM::
|
|
3
|
+
class LLM::Google
|
|
4
4
|
##
|
|
5
|
-
# The {LLM::
|
|
5
|
+
# The {LLM::Google::Audio LLM::Google::Audio} class provides an audio
|
|
6
6
|
# object for interacting with [Gemini's audio API](https://ai.google.dev/gemini-api/docs/audio).
|
|
7
7
|
# @example
|
|
8
8
|
# #!/usr/bin/env ruby
|
|
9
9
|
# require "llm"
|
|
10
10
|
#
|
|
11
|
-
# llm = LLM.
|
|
11
|
+
# llm = LLM.google(key: ENV["KEY"])
|
|
12
12
|
# res = llm.audio.create_transcription(input: "/audio/rocket.mp3")
|
|
13
13
|
# res.text # => "A dog on a rocket to the moon"
|
|
14
14
|
class Audio
|
|
15
15
|
##
|
|
16
16
|
# Returns a new Audio object
|
|
17
17
|
# @param provider [LLM::Provider]
|
|
18
|
-
# @return [LLM::
|
|
18
|
+
# @return [LLM::Google::Audio]
|
|
19
19
|
def initialize(provider)
|
|
20
20
|
@provider = provider
|
|
21
21
|
end
|
|
@@ -30,7 +30,7 @@ class LLM::Gemini
|
|
|
30
30
|
##
|
|
31
31
|
# Create an audio transcription
|
|
32
32
|
# @example
|
|
33
|
-
# llm = LLM.
|
|
33
|
+
# llm = LLM.google(key: ENV["KEY"])
|
|
34
34
|
# res = llm.audio.create_transcription(file: "/audio/rocket.mp3")
|
|
35
35
|
# res.text # => "A dog on a rocket to the moon"
|
|
36
36
|
# @see https://ai.google.dev/gemini-api/docs/audio Gemini docs
|
|
@@ -52,7 +52,7 @@ class LLM::Gemini
|
|
|
52
52
|
# Create an audio translation (in English)
|
|
53
53
|
# @example
|
|
54
54
|
# # Arabic => English
|
|
55
|
-
# llm = LLM.
|
|
55
|
+
# llm = LLM.google(key: ENV["KEY"])
|
|
56
56
|
# res = llm.audio.create_translation(file: "/audio/bismillah.mp3")
|
|
57
57
|
# res.text # => "In the name of Allah, the Beneficent, the Merciful."
|
|
58
58
|
# @see https://ai.google.dev/gemini-api/docs/audio Gemini docs
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
class LLM::
|
|
3
|
+
class LLM::Google
|
|
4
4
|
##
|
|
5
5
|
# @private
|
|
6
6
|
class ErrorHandler
|
|
@@ -21,7 +21,7 @@ class LLM::Gemini
|
|
|
21
21
|
# The span
|
|
22
22
|
# @param [Net::HTTPResponse] res
|
|
23
23
|
# The response from the server
|
|
24
|
-
# @return [LLM::
|
|
24
|
+
# @return [LLM::Google::ErrorHandler]
|
|
25
25
|
def initialize(tracer, span, res)
|
|
26
26
|
@tracer = tracer
|
|
27
27
|
@span = span
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
class LLM::
|
|
3
|
+
class LLM::Google
|
|
4
4
|
##
|
|
5
|
-
# The {LLM::
|
|
5
|
+
# The {LLM::Google::Files LLM::Google::Files} class provides a files
|
|
6
6
|
# object for interacting with [Gemini's Files API](https://ai.google.dev/gemini-api/docs/files).
|
|
7
7
|
# The files API allows a client to reference media files in prompts
|
|
8
8
|
# where they can be referenced by their URL.
|
|
@@ -17,16 +17,16 @@ class LLM::Gemini
|
|
|
17
17
|
# #!/usr/bin/env ruby
|
|
18
18
|
# require "llm"
|
|
19
19
|
#
|
|
20
|
-
# llm = LLM.
|
|
21
|
-
#
|
|
20
|
+
# llm = LLM.google(key: ENV["KEY"])
|
|
21
|
+
# ctx = LLM::Context.new(llm)
|
|
22
22
|
# file = llm.files.create(file: "/audio/haiku.mp3")
|
|
23
|
-
#
|
|
24
|
-
#
|
|
23
|
+
# ctx.talk ["Tell me about this file", file]
|
|
24
|
+
# ctx.messages.select(&:assistant?).each { print "[#{_1.role}]", _1.content, "\n" }
|
|
25
25
|
class Files
|
|
26
26
|
##
|
|
27
27
|
# Returns a new Files object
|
|
28
28
|
# @param provider [LLM::Provider]
|
|
29
|
-
# @return [LLM::
|
|
29
|
+
# @return [LLM::Google::Files]
|
|
30
30
|
def initialize(provider)
|
|
31
31
|
@provider = provider
|
|
32
32
|
end
|
|
@@ -34,7 +34,7 @@ class LLM::Gemini
|
|
|
34
34
|
##
|
|
35
35
|
# List all files
|
|
36
36
|
# @example
|
|
37
|
-
# llm = LLM.
|
|
37
|
+
# llm = LLM.google(key: ENV["KEY"])
|
|
38
38
|
# res = llm.files.all
|
|
39
39
|
# res.each do |file|
|
|
40
40
|
# print "name: ", file.name, "\n"
|
|
@@ -55,7 +55,7 @@ class LLM::Gemini
|
|
|
55
55
|
##
|
|
56
56
|
# Create a file
|
|
57
57
|
# @example
|
|
58
|
-
# llm = LLM.
|
|
58
|
+
# llm = LLM.google(key: ENV["KEY"])
|
|
59
59
|
# res = llm.files.create(file: "/audio/haiku.mp3")
|
|
60
60
|
# @see https://ai.google.dev/gemini-api/docs/files Gemini docs
|
|
61
61
|
# @param [String, LLM::File] file The file
|
|
@@ -80,7 +80,7 @@ class LLM::Gemini
|
|
|
80
80
|
##
|
|
81
81
|
# Get a file
|
|
82
82
|
# @example
|
|
83
|
-
# llm = LLM.
|
|
83
|
+
# llm = LLM.google(key: ENV["KEY"])
|
|
84
84
|
# res = llm.files.get(file: "files/1234567890")
|
|
85
85
|
# print "name: ", res.name, "\n"
|
|
86
86
|
# @see https://ai.google.dev/gemini-api/docs/files Gemini docs
|
|
@@ -101,7 +101,7 @@ class LLM::Gemini
|
|
|
101
101
|
##
|
|
102
102
|
# Delete a file
|
|
103
103
|
# @example
|
|
104
|
-
# llm = LLM.
|
|
104
|
+
# llm = LLM.google(key: ENV["KEY"])
|
|
105
105
|
# res = llm.files.delete(file: "files/1234567890")
|
|
106
106
|
# @see https://ai.google.dev/gemini-api/docs/files Gemini docs
|
|
107
107
|
# @param [#name, String] file The file to delete
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
class LLM::
|
|
3
|
+
class LLM::Google
|
|
4
4
|
##
|
|
5
|
-
# The {LLM::
|
|
5
|
+
# The {LLM::Google::Images LLM::Google::Images} class provides an images
|
|
6
6
|
# object for interacting with Google's Imagen text-to-image models via the
|
|
7
7
|
# Imagen API: https://ai.google.dev/gemini-api/docs/imagen
|
|
8
8
|
#
|
|
9
9
|
# @example
|
|
10
10
|
# #!/usr/bin/env ruby
|
|
11
11
|
# require "llm"
|
|
12
|
-
# llm = LLM.
|
|
12
|
+
# llm = LLM.google(key: ENV["KEY"])
|
|
13
13
|
# res = llm.images.create prompt: "A dog on a rocket to the moon"
|
|
14
14
|
# IO.copy_stream res.images[0], "rocket.png"
|
|
15
15
|
class Images
|
|
@@ -18,7 +18,7 @@ class LLM::Gemini
|
|
|
18
18
|
##
|
|
19
19
|
# Returns a new Images object
|
|
20
20
|
# @param provider [LLM::Provider]
|
|
21
|
-
# @return [LLM::
|
|
21
|
+
# @return [LLM::Google::Images]
|
|
22
22
|
def initialize(provider)
|
|
23
23
|
@provider = provider
|
|
24
24
|
end
|
|
@@ -26,7 +26,7 @@ class LLM::Gemini
|
|
|
26
26
|
##
|
|
27
27
|
# Create an image
|
|
28
28
|
# @example
|
|
29
|
-
# llm = LLM.
|
|
29
|
+
# llm = LLM.google(key: ENV["KEY"])
|
|
30
30
|
# res = llm.images.create prompt: "A dog on a rocket to the moon"
|
|
31
31
|
# IO.copy_stream res.images[0], "rocket.png"
|
|
32
32
|
# @see https://ai.google.dev/gemini-api/docs/imagen Imagen docs
|
|
@@ -60,7 +60,7 @@ class LLM::Gemini
|
|
|
60
60
|
##
|
|
61
61
|
# Edit an image
|
|
62
62
|
# @example
|
|
63
|
-
# llm = LLM.
|
|
63
|
+
# llm = LLM.google(key: ENV["KEY"])
|
|
64
64
|
# res = llm.images.edit image: "cat.png", prompt: "Add a hat to the cat"
|
|
65
65
|
# IO.copy_stream res.images[0], "hatoncat.png"
|
|
66
66
|
# @see https://ai.google.dev/gemini-api/docs/image-generation Gemini docs
|
|
@@ -68,7 +68,7 @@ class LLM::Gemini
|
|
|
68
68
|
# @param [String] prompt The prompt
|
|
69
69
|
# @param [Hash] params Other parameters (see Gemini docs)
|
|
70
70
|
# @raise (see LLM::Provider#request)
|
|
71
|
-
# @note (see LLM::
|
|
71
|
+
# @note (see LLM::Google::Images#create)
|
|
72
72
|
# @return [LLM::Response]
|
|
73
73
|
def edit(image:, prompt:, model: "gemini-2.5-flash-image", **params)
|
|
74
74
|
raise NotImplementedError, "image editing is not yet supported by Gemini"
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
class LLM::
|
|
3
|
+
class LLM::Google
|
|
4
4
|
##
|
|
5
|
-
# The {LLM::
|
|
5
|
+
# The {LLM::Google::Models LLM::Google::Models} class provides a model
|
|
6
6
|
# object for interacting with [Gemini's models API](https://ai.google.dev/api/models?hl=en#method:-models.list).
|
|
7
7
|
# The models API allows a client to query Gemini for a list of models
|
|
8
8
|
# that are available for use with the Gemini API.
|
|
@@ -11,7 +11,7 @@ class LLM::Gemini
|
|
|
11
11
|
# #!/usr/bin/env ruby
|
|
12
12
|
# require "llm"
|
|
13
13
|
#
|
|
14
|
-
# llm = LLM.
|
|
14
|
+
# llm = LLM.google(key: ENV["KEY"])
|
|
15
15
|
# res = llm.models.all
|
|
16
16
|
# res.each do |model|
|
|
17
17
|
# print "id: ", model.id, "\n"
|
|
@@ -22,7 +22,7 @@ class LLM::Gemini
|
|
|
22
22
|
##
|
|
23
23
|
# Returns a new Models object
|
|
24
24
|
# @param provider [LLM::Provider]
|
|
25
|
-
# @return [LLM::
|
|
25
|
+
# @return [LLM::Google::Models]
|
|
26
26
|
def initialize(provider)
|
|
27
27
|
@provider = provider
|
|
28
28
|
end
|
|
@@ -30,7 +30,7 @@ class LLM::Gemini
|
|
|
30
30
|
##
|
|
31
31
|
# List all models
|
|
32
32
|
# @example
|
|
33
|
-
# llm = LLM.
|
|
33
|
+
# llm = LLM.google(key: ENV["KEY"])
|
|
34
34
|
# res = llm.models.all
|
|
35
35
|
# res.each do |model|
|
|
36
36
|
# print "id: ", model.id, "\n"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
module LLM::
|
|
3
|
+
module LLM::Google::RequestAdapter
|
|
4
4
|
##
|
|
5
5
|
# @private
|
|
6
6
|
class Completion
|
|
@@ -19,7 +19,7 @@ module LLM::Gemini::RequestAdapter
|
|
|
19
19
|
if Hash === message
|
|
20
20
|
{role: message[:role], parts: adapt_content(message[:content])}
|
|
21
21
|
elsif message.tool_call?
|
|
22
|
-
{role: message.role, parts: message.extra
|
|
22
|
+
{role: message.role, parts: message.extra.original_tool_calls}
|
|
23
23
|
else
|
|
24
24
|
{role: message.role, parts: adapt_content(message.content)}
|
|
25
25
|
end
|
|
@@ -37,7 +37,7 @@ module LLM::Gemini::RequestAdapter
|
|
|
37
37
|
when LLM::Message
|
|
38
38
|
adapt_content(content.content)
|
|
39
39
|
when LLM::Function::Return
|
|
40
|
-
[{functionResponse: {name: content.name, response: content.value}}]
|
|
40
|
+
[{functionResponse: {name: content.name, response: adapt_function_response(content.value)}}]
|
|
41
41
|
when LLM::Object
|
|
42
42
|
adapt_object(content)
|
|
43
43
|
else
|
|
@@ -64,6 +64,10 @@ module LLM::Gemini::RequestAdapter
|
|
|
64
64
|
[{file_data: {mime_type: file.mime_type, file_uri: file.uri}}]
|
|
65
65
|
end
|
|
66
66
|
|
|
67
|
+
def adapt_function_response(value)
|
|
68
|
+
Hash === value ? value : {result: value}
|
|
69
|
+
end
|
|
70
|
+
|
|
67
71
|
def prompt_error!(object)
|
|
68
72
|
if LLM::Object === object
|
|
69
73
|
raise LLM::PromptError, "The given LLM::Object with kind '#{content.kind}' is not " \
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
module LLM::
|
|
3
|
+
module LLM::Google::ResponseAdapter
|
|
4
4
|
module Completion
|
|
5
5
|
##
|
|
6
6
|
# (see LLM::Contract::Completion#messages)
|
|
@@ -64,17 +64,17 @@ module LLM::Gemini::ResponseAdapter
|
|
|
64
64
|
content = choice.content || LLM::Object.new
|
|
65
65
|
role = content.role || "model"
|
|
66
66
|
parts = content.parts || [{"text" => choice.finishReason}]
|
|
67
|
-
text
|
|
68
|
-
tools = parts.
|
|
67
|
+
text = parts.filter_map { _1["text"] }.join
|
|
68
|
+
tools = parts.select { _1["functionCall"] }
|
|
69
69
|
extra = {index:, response: self, tool_calls: adapt_tool_calls(tools), original_tool_calls: tools}
|
|
70
70
|
LLM::Message.new(role, text, extra)
|
|
71
71
|
end
|
|
72
72
|
end
|
|
73
73
|
|
|
74
|
-
def adapt_tool_calls(
|
|
75
|
-
(
|
|
76
|
-
|
|
77
|
-
|
|
74
|
+
def adapt_tool_calls(parts)
|
|
75
|
+
(parts || []).map do |part|
|
|
76
|
+
tool = part["functionCall"]
|
|
77
|
+
{name: tool.name, arguments: tool.args}
|
|
78
78
|
end
|
|
79
79
|
end
|
|
80
80
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
module LLM::
|
|
3
|
+
module LLM::Google::ResponseAdapter
|
|
4
4
|
##
|
|
5
|
-
# The {LLM::
|
|
5
|
+
# The {LLM::Google::ResponseAdapter::WebSearch LLM::Google::ResponseAdapter::WebSearch}
|
|
6
6
|
# module provides methods for accessing web search results from a web search
|
|
7
7
|
# tool call made via the {LLM::Provider#web_search LLM::Provider#web_search}
|
|
8
8
|
# method.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
class LLM::
|
|
3
|
+
class LLM::Google
|
|
4
4
|
##
|
|
5
5
|
# @private
|
|
6
6
|
module ResponseAdapter
|
|
@@ -27,13 +27,13 @@ class LLM::Gemini
|
|
|
27
27
|
# @api private
|
|
28
28
|
def select(type)
|
|
29
29
|
case type
|
|
30
|
-
when :completion then LLM::
|
|
31
|
-
when :embedding then LLM::
|
|
32
|
-
when :file then LLM::
|
|
33
|
-
when :files then LLM::
|
|
34
|
-
when :image then LLM::
|
|
35
|
-
when :models then LLM::
|
|
36
|
-
when :web_search then LLM::
|
|
30
|
+
when :completion then LLM::Google::ResponseAdapter::Completion
|
|
31
|
+
when :embedding then LLM::Google::ResponseAdapter::Embedding
|
|
32
|
+
when :file then LLM::Google::ResponseAdapter::File
|
|
33
|
+
when :files then LLM::Google::ResponseAdapter::Files
|
|
34
|
+
when :image then LLM::Google::ResponseAdapter::Image
|
|
35
|
+
when :models then LLM::Google::ResponseAdapter::Models
|
|
36
|
+
when :web_search then LLM::Google::ResponseAdapter::WebSearch
|
|
37
37
|
else
|
|
38
38
|
raise ArgumentError, "Unknown response adapter type: #{type.inspect}"
|
|
39
39
|
end
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
class LLM::
|
|
3
|
+
class LLM::Google
|
|
4
4
|
##
|
|
5
5
|
# @private
|
|
6
6
|
class StreamParser
|
|
@@ -11,7 +11,7 @@ class LLM::Gemini
|
|
|
11
11
|
|
|
12
12
|
##
|
|
13
13
|
# @param [#<<] io An IO-like object
|
|
14
|
-
# @return [LLM::
|
|
14
|
+
# @return [LLM::Google::StreamParser]
|
|
15
15
|
def initialize(io)
|
|
16
16
|
@body = {"candidates" => []}
|
|
17
17
|
@io = io
|
|
@@ -19,7 +19,7 @@ class LLM::Gemini
|
|
|
19
19
|
|
|
20
20
|
##
|
|
21
21
|
# @param [Hash] chunk
|
|
22
|
-
# @return [LLM::
|
|
22
|
+
# @return [LLM::Google::StreamParser]
|
|
23
23
|
def parse!(chunk)
|
|
24
24
|
tap { merge_chunk!(chunk) }
|
|
25
25
|
end
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
module LLM
|
|
4
4
|
##
|
|
5
|
-
# The
|
|
6
|
-
# [Gemini](https://ai.google.dev/). The
|
|
5
|
+
# The Google class implements a provider for
|
|
6
|
+
# [Gemini](https://ai.google.dev/). The Google provider
|
|
7
7
|
# can accept multiple inputs (text, images, audio, and video).
|
|
8
8
|
# The inputs can be provided inline via the prompt for files
|
|
9
9
|
# under 20MB or via the Gemini Files API for files
|
|
@@ -13,19 +13,19 @@ module LLM
|
|
|
13
13
|
# #!/usr/bin/env ruby
|
|
14
14
|
# require "llm"
|
|
15
15
|
#
|
|
16
|
-
# llm = LLM.
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
class
|
|
21
|
-
require_relative "
|
|
22
|
-
require_relative "
|
|
23
|
-
require_relative "
|
|
24
|
-
require_relative "
|
|
25
|
-
require_relative "
|
|
26
|
-
require_relative "
|
|
27
|
-
require_relative "
|
|
28
|
-
require_relative "
|
|
16
|
+
# llm = LLM.google(key: ENV["KEY"])
|
|
17
|
+
# ctx = LLM::Context.new(llm)
|
|
18
|
+
# ctx.talk ["Tell me about this photo", ctx.local_file("/images/photo.png")]
|
|
19
|
+
# ctx.messages.select(&:assistant?).each { print "[#{_1.role}]", _1.content, "\n" }
|
|
20
|
+
class Google < Provider
|
|
21
|
+
require_relative "google/error_handler"
|
|
22
|
+
require_relative "google/request_adapter"
|
|
23
|
+
require_relative "google/response_adapter"
|
|
24
|
+
require_relative "google/stream_parser"
|
|
25
|
+
require_relative "google/models"
|
|
26
|
+
require_relative "google/images"
|
|
27
|
+
require_relative "google/audio"
|
|
28
|
+
require_relative "google/files"
|
|
29
29
|
|
|
30
30
|
include RequestAdapter
|
|
31
31
|
|
|
@@ -37,6 +37,13 @@ module LLM
|
|
|
37
37
|
super(host: HOST, **)
|
|
38
38
|
end
|
|
39
39
|
|
|
40
|
+
##
|
|
41
|
+
# @return [Symbol]
|
|
42
|
+
# Returns the provider's name
|
|
43
|
+
def name
|
|
44
|
+
:google
|
|
45
|
+
end
|
|
46
|
+
|
|
40
47
|
##
|
|
41
48
|
# Provides an embedding
|
|
42
49
|
# @param input (see LLM::Provider#embed)
|
|
@@ -78,33 +85,33 @@ module LLM
|
|
|
78
85
|
##
|
|
79
86
|
# Provides an interface to Gemini's audio API
|
|
80
87
|
# @see https://ai.google.dev/gemini-api/docs/audio Gemini docs
|
|
81
|
-
# @return [LLM::
|
|
88
|
+
# @return [LLM::Google::Audio]
|
|
82
89
|
def audio
|
|
83
|
-
LLM::
|
|
90
|
+
LLM::Google::Audio.new(self)
|
|
84
91
|
end
|
|
85
92
|
|
|
86
93
|
##
|
|
87
94
|
# Provides an interface to Gemini's image generation API
|
|
88
95
|
# @see https://ai.google.dev/gemini-api/docs/image-generation Gemini docs
|
|
89
|
-
# @return [see LLM::
|
|
96
|
+
# @return [see LLM::Google::Images]
|
|
90
97
|
def images
|
|
91
|
-
LLM::
|
|
98
|
+
LLM::Google::Images.new(self)
|
|
92
99
|
end
|
|
93
100
|
|
|
94
101
|
##
|
|
95
102
|
# Provides an interface to Gemini's file management API
|
|
96
103
|
# @see https://ai.google.dev/gemini-api/docs/files Gemini docs
|
|
97
|
-
# @return [LLM::
|
|
104
|
+
# @return [LLM::Google::Files]
|
|
98
105
|
def files
|
|
99
|
-
LLM::
|
|
106
|
+
LLM::Google::Files.new(self)
|
|
100
107
|
end
|
|
101
108
|
|
|
102
109
|
##
|
|
103
110
|
# Provides an interface to Gemini's models API
|
|
104
111
|
# @see https://ai.google.dev/gemini-api/docs/models Gemini docs
|
|
105
|
-
# @return [LLM::
|
|
112
|
+
# @return [LLM::Google::Models]
|
|
106
113
|
def models
|
|
107
|
-
LLM::
|
|
114
|
+
LLM::Google::Models.new(self)
|
|
108
115
|
end
|
|
109
116
|
|
|
110
117
|
##
|
|
@@ -177,11 +184,11 @@ module LLM
|
|
|
177
184
|
end
|
|
178
185
|
|
|
179
186
|
def stream_parser
|
|
180
|
-
LLM::
|
|
187
|
+
LLM::Google::StreamParser
|
|
181
188
|
end
|
|
182
189
|
|
|
183
190
|
def error_handler
|
|
184
|
-
LLM::
|
|
191
|
+
LLM::Google::ErrorHandler
|
|
185
192
|
end
|
|
186
193
|
|
|
187
194
|
def normalize_complete_params(params)
|
|
@@ -197,10 +204,18 @@ module LLM
|
|
|
197
204
|
model.respond_to?(:id) ? model.id : model
|
|
198
205
|
path = ["/v1beta/models/#{model}", action].join(":")
|
|
199
206
|
req = Net::HTTP::Post.new(path, headers)
|
|
200
|
-
messages =
|
|
207
|
+
messages = build_complete_messages(prompt, params, role)
|
|
201
208
|
body = LLM.json.dump({contents: adapt(messages)}.merge!(params))
|
|
202
209
|
set_body_stream(req, StringIO.new(body))
|
|
203
210
|
req
|
|
204
211
|
end
|
|
212
|
+
|
|
213
|
+
def build_complete_messages(prompt, params, role)
|
|
214
|
+
if LLM::Prompt === prompt
|
|
215
|
+
[*(params.delete(:messages) || []), *prompt.to_a]
|
|
216
|
+
else
|
|
217
|
+
[*(params.delete(:messages) || []), LLM::Message.new(role, prompt)]
|
|
218
|
+
end
|
|
219
|
+
end
|
|
205
220
|
end
|
|
206
221
|
end
|
|
@@ -16,9 +16,9 @@ module LLM
|
|
|
16
16
|
# require "llm"
|
|
17
17
|
#
|
|
18
18
|
# llm = LLM.llamacpp(key: nil)
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
#
|
|
19
|
+
# ctx = LLM::Context.new(llm)
|
|
20
|
+
# ctx.talk ["Tell me about this photo", ctx.local_file("/images/photo.png")]
|
|
21
|
+
# ctx.messages.select(&:assistant?).each { print "[#{_1.role}]", _1.content, "\n" }
|
|
22
22
|
class LlamaCpp < OpenAI
|
|
23
23
|
##
|
|
24
24
|
# @param (see LLM::Provider#initialize)
|
|
@@ -27,6 +27,13 @@ module LLM
|
|
|
27
27
|
super
|
|
28
28
|
end
|
|
29
29
|
|
|
30
|
+
##
|
|
31
|
+
# @return [Symbol]
|
|
32
|
+
# Returns the provider's name
|
|
33
|
+
def name
|
|
34
|
+
:llamacpp
|
|
35
|
+
end
|
|
36
|
+
|
|
30
37
|
##
|
|
31
38
|
# @raise [NotImplementedError]
|
|
32
39
|
def files
|
data/lib/llm/providers/ollama.rb
CHANGED
|
@@ -12,9 +12,9 @@ module LLM
|
|
|
12
12
|
# require "llm"
|
|
13
13
|
#
|
|
14
14
|
# llm = LLM.ollama(key: nil)
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
15
|
+
# ctx = LLM::Context.new(llm, model: "llava")
|
|
16
|
+
# ctx.talk ["Tell me about this image", ctx.local_file("/images/photo.png")]
|
|
17
|
+
# ctx.messages.select(&:assistant?).each { print "[#{_1.role}]", _1.content, "\n" }
|
|
18
18
|
class Ollama < Provider
|
|
19
19
|
require_relative "ollama/error_handler"
|
|
20
20
|
require_relative "ollama/request_adapter"
|
|
@@ -32,6 +32,13 @@ module LLM
|
|
|
32
32
|
super(host: HOST, port: 11434, ssl: false, **)
|
|
33
33
|
end
|
|
34
34
|
|
|
35
|
+
##
|
|
36
|
+
# @return [Symbol]
|
|
37
|
+
# Returns the provider's name
|
|
38
|
+
def name
|
|
39
|
+
:ollama
|
|
40
|
+
end
|
|
41
|
+
|
|
35
42
|
##
|
|
36
43
|
# Provides an embedding
|
|
37
44
|
# @param input (see LLM::Provider#embed)
|
|
@@ -120,11 +127,19 @@ module LLM
|
|
|
120
127
|
end
|
|
121
128
|
|
|
122
129
|
def build_complete_request(prompt, params, role)
|
|
123
|
-
messages =
|
|
130
|
+
messages = build_complete_messages(prompt, params, role)
|
|
124
131
|
body = LLM.json.dump({messages: [adapt(messages)].flatten}.merge!(params))
|
|
125
132
|
req = Net::HTTP::Post.new("/api/chat", headers)
|
|
126
133
|
set_body_stream(req, StringIO.new(body))
|
|
127
134
|
req
|
|
128
135
|
end
|
|
136
|
+
|
|
137
|
+
def build_complete_messages(prompt, params, role)
|
|
138
|
+
if LLM::Prompt === prompt
|
|
139
|
+
[*(params.delete(:messages) || []), *prompt.to_a]
|
|
140
|
+
else
|
|
141
|
+
[*(params.delete(:messages) || []), LLM::Message.new(role, prompt)]
|
|
142
|
+
end
|
|
143
|
+
end
|
|
129
144
|
end
|
|
130
145
|
end
|
|
@@ -13,10 +13,10 @@ class LLM::OpenAI
|
|
|
13
13
|
# require "llm"
|
|
14
14
|
#
|
|
15
15
|
# llm = LLM.openai(key: ENV["KEY"])
|
|
16
|
-
#
|
|
16
|
+
# ctx = LLM::Context.new(llm)
|
|
17
17
|
# file = llm.files.create file: "/books/goodread.pdf"
|
|
18
|
-
#
|
|
19
|
-
#
|
|
18
|
+
# ctx.talk ["Tell me about this PDF", file]
|
|
19
|
+
# ctx.messages.select(&:assistant?).each { print "[#{_1.role}]", _1.content, "\n" }
|
|
20
20
|
class Files
|
|
21
21
|
##
|
|
22
22
|
# Returns a new Files object
|
|
@@ -74,10 +74,18 @@ module LLM::OpenAI::ResponseAdapter
|
|
|
74
74
|
def adapt_tool_calls(tools)
|
|
75
75
|
(tools || []).filter_map do |tool|
|
|
76
76
|
next unless tool.function
|
|
77
|
-
{id: tool.id, name: tool.function.name, arguments:
|
|
77
|
+
{id: tool.id, name: tool.function.name, arguments: parse_tool_arguments(tool.function.arguments)}
|
|
78
78
|
end
|
|
79
79
|
end
|
|
80
80
|
|
|
81
|
+
def parse_tool_arguments(arguments)
|
|
82
|
+
return {} if arguments.to_s.empty?
|
|
83
|
+
parsed = LLM.json.load(arguments)
|
|
84
|
+
Hash === parsed ? parsed : {}
|
|
85
|
+
rescue *LLM.json.parser_error
|
|
86
|
+
{}
|
|
87
|
+
end
|
|
88
|
+
|
|
81
89
|
include LLM::Contract::Completion
|
|
82
90
|
end
|
|
83
91
|
end
|