async-ollama 0.3.0 → 0.5.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
- checksums.yaml.gz.sig +0 -0
- data/lib/async/ollama/chat.rb +81 -0
- data/lib/async/ollama/client.rb +34 -4
- data/lib/async/ollama/conversation.rb +119 -0
- data/lib/async/ollama/generate.rb +4 -15
- data/lib/async/ollama/models.rb +19 -0
- data/lib/async/ollama/toolbox.rb +93 -0
- data/lib/async/ollama/version.rb +1 -1
- data/lib/async/ollama/wrapper.rb +126 -54
- data/lib/async/ollama.rb +10 -1
- data/license.md +1 -1
- data/readme.md +9 -0
- data/releases.md +6 -0
- data.tar.gz.sig +0 -0
- metadata +13 -13
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6ccd3b807e6fb1863176fe45a44a41176cc122d72c9650ef0e96eb748151c2a9
|
4
|
+
data.tar.gz: 28a590bb9f9a62e12419a6c5e6c1c6b1661cee0a0b1068a795c3ebcfb9422282
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 68119787b00471de152fb5d99a3d94c6a86c20216925cb36cb8ed9c88bb694861d0b293dee7594ad411b65894a03ecf2e18c775708db9465c4341008fee3615e
|
7
|
+
data.tar.gz: 1dca4d6db7fd5cc6136f6a65ef5b61777ed6c720d9d7ec445d7d7443c1a4d6769c552939a02cc4fa306de93038c0cfff6ef7b328061130716c5280cfbb836fef
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Released under the MIT License.
|
4
|
+
# Copyright, 2025, by Samuel Williams.
|
5
|
+
|
6
|
+
require "async/rest/representation"
|
7
|
+
require_relative "wrapper"
|
8
|
+
|
9
|
+
module Async
|
10
|
+
module Ollama
|
11
|
+
# Represents a chat response from the Ollama API, including message content, model, and timing information.
|
12
|
+
class Chat < Async::REST::Representation[ChatWrapper]
|
13
|
+
# @returns [Hash | nil] The message content, or nil if not present.
|
14
|
+
def message
|
15
|
+
self.value[:message]
|
16
|
+
end
|
17
|
+
|
18
|
+
# @returns [String | nil] The error message, or nil if not present.
|
19
|
+
def error
|
20
|
+
self.value[:error]
|
21
|
+
end
|
22
|
+
|
23
|
+
# @returns [Array(Hash) | nil] The tool calls, or nil if not present.
|
24
|
+
def tool_calls
|
25
|
+
if message = self.message
|
26
|
+
message[:tool_calls]
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# @returns [String] The model name used to generate this response.
|
31
|
+
def model
|
32
|
+
self.value[:model]
|
33
|
+
end
|
34
|
+
|
35
|
+
# @return [Integer | nil] The time spent generating the response, in nanoseconds.
|
36
|
+
def total_duration
|
37
|
+
self.value[:total_duration]
|
38
|
+
end
|
39
|
+
|
40
|
+
# @return [Integer | nil] The time spent loading the model, in nanoseconds.
|
41
|
+
def load_duration
|
42
|
+
self.value[:load_duration]
|
43
|
+
end
|
44
|
+
|
45
|
+
# @return [Integer | nil] The number of tokens in the prompt (the token count).
|
46
|
+
def prompt_eval_count
|
47
|
+
self.value[:prompt_eval_count]
|
48
|
+
end
|
49
|
+
|
50
|
+
# @return [Integer | nil] The time spent evaluating the prompt, in nanoseconds.
|
51
|
+
def prompt_eval_duration
|
52
|
+
self.value[:prompt_eval_duration]
|
53
|
+
end
|
54
|
+
|
55
|
+
# @return [Integer | nil] The number of tokens in the response.
|
56
|
+
def eval_count
|
57
|
+
self.value[:eval_count]
|
58
|
+
end
|
59
|
+
|
60
|
+
# @return [Integer | nil] The time spent generating the response, in nanoseconds.
|
61
|
+
def eval_duration
|
62
|
+
self.value[:eval_duration]
|
63
|
+
end
|
64
|
+
|
65
|
+
# @return [Integer] The sum of prompt and response token counts.
|
66
|
+
def token_count
|
67
|
+
count = 0
|
68
|
+
|
69
|
+
if prompt_eval_count = self.prompt_eval_count
|
70
|
+
count += prompt_eval_count
|
71
|
+
end
|
72
|
+
|
73
|
+
if eval_count = self.eval_count
|
74
|
+
count += eval_count
|
75
|
+
end
|
76
|
+
|
77
|
+
return count
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
data/lib/async/ollama/client.rb
CHANGED
@@ -1,23 +1,30 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Released under the MIT License.
|
4
|
-
# Copyright, 2024, by Samuel Williams.
|
4
|
+
# Copyright, 2024-2025, by Samuel Williams.
|
5
5
|
|
6
6
|
require "async/rest/resource"
|
7
|
+
|
7
8
|
require_relative "generate"
|
9
|
+
require_relative "chat"
|
10
|
+
require_relative "models"
|
8
11
|
|
9
12
|
module Async
|
10
13
|
module Ollama
|
11
|
-
|
14
|
+
MODEL = ENV.fetch("ASYNC_OLLAMA_MODEL", "llama3.1:latest")
|
15
|
+
|
16
|
+
# Represents a connection to the Ollama service, providing methods to generate completions, chat, and list models.
|
12
17
|
class Client < Async::REST::Resource
|
13
18
|
# The default endpoint to connect to.
|
14
19
|
ENDPOINT = Async::HTTP::Endpoint.parse("http://localhost:11434")
|
15
20
|
|
16
|
-
#
|
21
|
+
# Generates a response from the given prompt using Ollama.
|
17
22
|
# @parameter prompt [String] The prompt to generate a response from.
|
23
|
+
# @parameter options [Hash] Additional options for the request.
|
24
|
+
# @returns [Generate] The generated response representation.
|
18
25
|
def generate(prompt, **options, &block)
|
19
26
|
options[:prompt] = prompt
|
20
|
-
options[:model] ||=
|
27
|
+
options[:model] ||= MODEL
|
21
28
|
|
22
29
|
Generate.post(self.with(path: "/api/generate"), options) do |resource, response|
|
23
30
|
if block_given?
|
@@ -27,6 +34,29 @@ module Async
|
|
27
34
|
Generate.new(resource, value: response.read, metadata: response.headers)
|
28
35
|
end
|
29
36
|
end
|
37
|
+
|
38
|
+
# Sends a chat request with the given messages to Ollama.
|
39
|
+
# @parameter messages [Array(Hash)] The chat messages to send.
|
40
|
+
# @parameter options [Hash] Additional options for the request.
|
41
|
+
# @returns [Chat] The chat response representation.
|
42
|
+
def chat(messages, **options, &block)
|
43
|
+
options[:model] ||= MODEL
|
44
|
+
options[:messages] = messages
|
45
|
+
|
46
|
+
Chat.post(self.with(path: "/api/chat"), options) do |resource, response|
|
47
|
+
if block_given?
|
48
|
+
yield response
|
49
|
+
end
|
50
|
+
|
51
|
+
Chat.new(resource, value: response.read, metadata: response.headers)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# Retrieves the list of available models from Ollama.
|
56
|
+
# @returns [Models] The models response representation.
|
57
|
+
def models
|
58
|
+
Models.get(self.with(path: "/api/tags"))
|
59
|
+
end
|
30
60
|
end
|
31
61
|
end
|
32
62
|
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Released under the MIT License.
|
4
|
+
# Copyright, 2024-2025, by Samuel Williams.
|
5
|
+
|
6
|
+
require_relative "client"
|
7
|
+
require_relative "toolbox"
|
8
|
+
|
9
|
+
module Async
|
10
|
+
module Ollama
|
11
|
+
# Represents a conversation with the Ollama API, managing messages, tool calls, and summarization.
|
12
|
+
class Conversation
|
13
|
+
# Raised when a chat error occurs during the conversation.
|
14
|
+
class ChatError < StandardError
|
15
|
+
end
|
16
|
+
|
17
|
+
# Initializes a new conversation.
|
18
|
+
# @parameter client [Client] The Ollama client instance.
|
19
|
+
# @parameter model [String] The model to use for the conversation.
|
20
|
+
# @parameter messages [Array(Hash)] The initial messages for the conversation.
|
21
|
+
# @parameter options [Hash] Additional options for the conversation.
|
22
|
+
def initialize(client, model: MODEL, messages: [], **options)
|
23
|
+
@client = client
|
24
|
+
@model = model
|
25
|
+
@messages = messages
|
26
|
+
@options = options
|
27
|
+
|
28
|
+
@toolbox = Toolbox.new
|
29
|
+
|
30
|
+
@last_response = nil
|
31
|
+
end
|
32
|
+
|
33
|
+
# @attribute [Toolbox] The toolbox for this conversation.
|
34
|
+
attr :toolbox
|
35
|
+
|
36
|
+
# @attribute [Array(Hash)] The messages in the conversation.
|
37
|
+
attr :messages
|
38
|
+
|
39
|
+
# @returns [Integer] The number of messages in the conversation.
|
40
|
+
def size
|
41
|
+
@messages.size
|
42
|
+
end
|
43
|
+
|
44
|
+
# @returns [Integer] The token count of the last response, or 0 if none.
|
45
|
+
def token_count
|
46
|
+
@last_response&.token_count || 0
|
47
|
+
end
|
48
|
+
|
49
|
+
# Sends a prompt to the conversation and processes the response, including tool calls.
|
50
|
+
# @parameter prompt [String | Hash] The prompt to send (as a string or message hash).
|
51
|
+
# @returns [Chat] The final chat response.
|
52
|
+
def call(prompt, &block)
|
53
|
+
if prompt.is_a?(String)
|
54
|
+
@messages << {
|
55
|
+
role: "user",
|
56
|
+
content: prompt
|
57
|
+
}
|
58
|
+
else
|
59
|
+
@messages << prompt
|
60
|
+
end
|
61
|
+
|
62
|
+
while true
|
63
|
+
@last_response = @client.chat(@messages, model: @model, tools: @toolbox.explain, options: @options, &block)
|
64
|
+
|
65
|
+
if error = @last_response.error
|
66
|
+
raise ChatError, error
|
67
|
+
end
|
68
|
+
|
69
|
+
@messages << @last_response.message
|
70
|
+
|
71
|
+
tool_calls = @last_response.tool_calls
|
72
|
+
|
73
|
+
if tool_calls.nil? || tool_calls.empty?
|
74
|
+
return @last_response
|
75
|
+
end
|
76
|
+
|
77
|
+
tool_calls.each do |tool_call|
|
78
|
+
@messages << @toolbox.call(tool_call)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
SUMMARIZE_MESSAGE = "Please summarize the conversation so far for your future reference. Do not introduce new information or questions. Refer to both user and assistant messages. Please keep the summary concise and relevant to the conversation and use it to continue the conversation."
|
84
|
+
|
85
|
+
# Summarizes the conversation and truncates messages to reduce context usage.
|
86
|
+
# @parameter retain [Integer] The number of messages to retain after summarization.
|
87
|
+
# @parameter role [String] The role to use for the summarization message.
|
88
|
+
# @returns [void]
|
89
|
+
def summarize!(retain = -1, role: "user")
|
90
|
+
current_size = @messages.size
|
91
|
+
|
92
|
+
# In principle, this could generate several messages:
|
93
|
+
message = {
|
94
|
+
role: role,
|
95
|
+
content: SUMMARIZE_MESSAGE,
|
96
|
+
}
|
97
|
+
|
98
|
+
self.call(message)
|
99
|
+
|
100
|
+
# The number of messages generated by the summarization:
|
101
|
+
delta = @messages.size - current_size
|
102
|
+
|
103
|
+
# After much experimentation, I found that leaving the SUMMARIZE_MESSAGE in the message stream caused extreme confusion, so we set retain to -1 to remove it by default.
|
104
|
+
retain += delta
|
105
|
+
if retain < @messages.size
|
106
|
+
truncated = @messages.size - retain
|
107
|
+
|
108
|
+
# We need to truncate the messages:
|
109
|
+
@messages = @messages.last(retain)
|
110
|
+
|
111
|
+
@messages.unshift({
|
112
|
+
role: "system",
|
113
|
+
content: "#{truncated} previous messages that have been removed and summarized to reduce context usage. Continue the conversation naturally as if the previous messages were still present.",
|
114
|
+
})
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
@@ -1,35 +1,24 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Released under the MIT License.
|
4
|
-
# Copyright, 2024, by Samuel Williams.
|
4
|
+
# Copyright, 2024-2025, by Samuel Williams.
|
5
5
|
|
6
6
|
require "async/rest/representation"
|
7
7
|
require_relative "wrapper"
|
8
8
|
|
9
9
|
module Async
|
10
10
|
module Ollama
|
11
|
+
# Represents a generated response from the Ollama API.
|
11
12
|
class Generate < Async::REST::Representation[Wrapper]
|
12
|
-
# The response
|
13
|
+
# @returns [String | nil] The generated response, or nil if not present.
|
13
14
|
def response
|
14
15
|
self.value[:response]
|
15
16
|
end
|
16
17
|
|
17
|
-
# The
|
18
|
-
def context
|
19
|
-
self.value[:context]
|
20
|
-
end
|
21
|
-
|
22
|
-
# The model used to generate the response.
|
18
|
+
# @returns [String] The model name used to generate the response.
|
23
19
|
def model
|
24
20
|
self.value[:model]
|
25
21
|
end
|
26
|
-
|
27
|
-
# Generate a new response from the given prompt.
|
28
|
-
# @parameter prompt [String] The prompt to generate a response from.
|
29
|
-
# @yields {|response| ...} Optional streaming response.
|
30
|
-
def generate(prompt, &block)
|
31
|
-
self.class.post(self.resource, prompt: prompt, context: self.context, model: self.model, &block)
|
32
|
-
end
|
33
22
|
end
|
34
23
|
end
|
35
24
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Released under the MIT License.
|
4
|
+
# Copyright, 2024-2025, by Samuel Williams.
|
5
|
+
|
6
|
+
require "async/rest/representation"
|
7
|
+
require_relative "wrapper"
|
8
|
+
|
9
|
+
module Async
|
10
|
+
module Ollama
|
11
|
+
# Represents the available models returned by the Ollama API.
|
12
|
+
class Models < Async::REST::Representation[Wrapper]
|
13
|
+
# @returns [Array(String)] The list of model names.
|
14
|
+
def names
|
15
|
+
self.value[:models].map {|model| model[:name]}
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Released under the MIT License.
|
4
|
+
# Copyright, 2024-2025, by Samuel Williams.
|
5
|
+
|
6
|
+
module Async
|
7
|
+
module Ollama
|
8
|
+
# Represents a tool that can be registered and called by the Toolbox.
|
9
|
+
class Tool
|
10
|
+
# Initializes a new tool with the given name, schema, and block.
|
11
|
+
# @parameter name [String] The name of the tool.
|
12
|
+
# @parameter schema [Hash] The schema describing the tool's function.
|
13
|
+
# @parameter block [Proc] The implementation of the tool.
|
14
|
+
def initialize(name, schema, &block)
|
15
|
+
@name = name
|
16
|
+
@schema = schema
|
17
|
+
@block = block
|
18
|
+
end
|
19
|
+
|
20
|
+
# @attribute [String] The name of the tool.
|
21
|
+
attr :name
|
22
|
+
|
23
|
+
# @attribute [Hash] The schema for the tool.
|
24
|
+
attr :schema
|
25
|
+
|
26
|
+
# Calls the tool with the given message.
|
27
|
+
# @parameter message [Hash] The message to process.
|
28
|
+
# @returns [Object] The result of the tool's block.
|
29
|
+
def call(message)
|
30
|
+
@block.call(message)
|
31
|
+
end
|
32
|
+
|
33
|
+
# @returns [Hash] The explanation of the tool's function for API usage.
|
34
|
+
def explain
|
35
|
+
{
|
36
|
+
type: "function",
|
37
|
+
function: @schema,
|
38
|
+
}
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Manages a collection of tools and dispatches calls to them.
|
43
|
+
class Toolbox
|
44
|
+
# Initializes a new, empty toolbox.
|
45
|
+
def initialize
|
46
|
+
@tools = {}
|
47
|
+
end
|
48
|
+
|
49
|
+
# @attribute [Hash] The registered tools by name.
|
50
|
+
attr :tools
|
51
|
+
|
52
|
+
# Registers a new tool with the given name, schema, and block.
|
53
|
+
# @parameter name [String] The name of the tool.
|
54
|
+
# @parameter schema [Hash] The schema describing the tool's function.
|
55
|
+
# @parameter block [Proc] The implementation of the tool.
|
56
|
+
def register(name, schema, &block)
|
57
|
+
@tools[name] = Tool.new(name, schema, &block)
|
58
|
+
end
|
59
|
+
|
60
|
+
# Calls a registered tool with the given message.
|
61
|
+
# @parameter message [Hash] The message containing the function call.
|
62
|
+
# @returns [Hash] The tool's response message.
|
63
|
+
def call(message)
|
64
|
+
function = message[:function]
|
65
|
+
name = function[:name]
|
66
|
+
|
67
|
+
if tool = @tools[name]
|
68
|
+
arguments = function[:arguments]
|
69
|
+
result = tool.call(arguments)
|
70
|
+
|
71
|
+
return {
|
72
|
+
role: "tool",
|
73
|
+
tool_name: name,
|
74
|
+
content: result.to_json,
|
75
|
+
}
|
76
|
+
else
|
77
|
+
raise ArgumentError.new("Unknown tool: #{name}")
|
78
|
+
end
|
79
|
+
rescue => error
|
80
|
+
return {
|
81
|
+
role: "tool",
|
82
|
+
tool_name: name,
|
83
|
+
error: error.inspect,
|
84
|
+
}
|
85
|
+
end
|
86
|
+
|
87
|
+
# @returns [Array(Hash)] The explanations for all registered tools.
|
88
|
+
def explain
|
89
|
+
@tools.values.map(&:explain)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
data/lib/async/ollama/version.rb
CHANGED
data/lib/async/ollama/wrapper.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Released under the MIT License.
|
4
|
-
# Copyright, 2024, by Samuel Williams.
|
4
|
+
# Copyright, 2024-2025, by Samuel Williams.
|
5
5
|
|
6
6
|
require "async/rest/wrapper/generic"
|
7
7
|
require "async/rest/wrapper/json"
|
@@ -13,10 +13,112 @@ require "json"
|
|
13
13
|
|
14
14
|
module Async
|
15
15
|
module Ollama
|
16
|
+
# Parses streaming HTTP responses for Ollama, buffering and extracting JSON lines.
|
17
|
+
class StreamingParser < ::Protocol::HTTP::Body::Wrapper
|
18
|
+
# @parameter args [Array] Arguments for the parent initializer.
|
19
|
+
def initialize(...)
|
20
|
+
super
|
21
|
+
|
22
|
+
@buffer = String.new.b
|
23
|
+
@offset = 0
|
24
|
+
|
25
|
+
@value = {}
|
26
|
+
end
|
27
|
+
|
28
|
+
# Reads the next JSON object from the stream.
|
29
|
+
# @returns [Hash | nil] The next parsed object, or nil if the stream is empty.
|
30
|
+
def read
|
31
|
+
return if @buffer.nil?
|
32
|
+
|
33
|
+
while true
|
34
|
+
if index = @buffer.index("\n", @offset)
|
35
|
+
line = @buffer.byteslice(@offset, index - @offset)
|
36
|
+
@buffer = @buffer.byteslice(index + 1, @buffer.bytesize - index - 1)
|
37
|
+
@offset = 0
|
38
|
+
|
39
|
+
return ::JSON.parse(line, symbolize_names: true)
|
40
|
+
end
|
41
|
+
|
42
|
+
if chunk = super
|
43
|
+
@buffer << chunk
|
44
|
+
else
|
45
|
+
return nil if @buffer.empty?
|
46
|
+
|
47
|
+
line = @buffer
|
48
|
+
@buffer = nil
|
49
|
+
@offset = 0
|
50
|
+
|
51
|
+
return ::JSON.parse(line, symbolize_names: true)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# Joins the stream, reading all objects and returning the final value.
|
57
|
+
# @returns [Hash] The final parsed value.
|
58
|
+
def join
|
59
|
+
self.each{}
|
60
|
+
|
61
|
+
return @value
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# Parses streaming responses for the Ollama API, collecting the response string.
|
66
|
+
class StreamingResponseParser < StreamingParser
|
67
|
+
# Initializes the parser with an empty response string.
|
68
|
+
def initialize(...)
|
69
|
+
super
|
70
|
+
|
71
|
+
@response = String.new
|
72
|
+
@value[:response] = @response
|
73
|
+
end
|
74
|
+
|
75
|
+
# Iterates over each response line, yielding the response string.
|
76
|
+
# @returns [Enumerator] Yields each response string.
|
77
|
+
def each
|
78
|
+
super do |line|
|
79
|
+
response = line.delete(:response)
|
80
|
+
@response << response
|
81
|
+
@value.merge!(line)
|
82
|
+
|
83
|
+
yield response
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# Parses streaming message responses for the Ollama API, collecting message content.
|
89
|
+
class StreamingMessageParser < StreamingParser
|
90
|
+
# Initializes the parser with an empty message content.
|
91
|
+
def initialize(...)
|
92
|
+
super
|
93
|
+
|
94
|
+
@content = String.new
|
95
|
+
@message = {content: @content, role: "assistant"}
|
96
|
+
@value[:message] = @message
|
97
|
+
end
|
98
|
+
|
99
|
+
# Iterates over each message line, yielding the message content.
|
100
|
+
# @returns [Enumerator] Yields each message content string.
|
101
|
+
def each
|
102
|
+
super do |line|
|
103
|
+
message = line.delete(:message)
|
104
|
+
content = message.delete(:content)
|
105
|
+
@content << content
|
106
|
+
@message.merge!(message)
|
107
|
+
@value.merge!(line)
|
108
|
+
|
109
|
+
yield content
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
# Wraps HTTP requests and responses for the Ollama API, handling content negotiation and parsing.
|
16
115
|
class Wrapper < Async::REST::Wrapper::Generic
|
17
116
|
APPLICATION_JSON = "application/json"
|
18
117
|
APPLICATION_JSON_STREAM = "application/x-ndjson"
|
19
118
|
|
119
|
+
# Prepares the HTTP request with appropriate headers and body.
|
120
|
+
# @parameter request [Protocol::HTTP::Request] The HTTP request object.
|
121
|
+
# @parameter payload [Protocol::HTTP::Response] The request payload.
|
20
122
|
def prepare_request(request, payload)
|
21
123
|
request.headers.add("accept", APPLICATION_JSON)
|
22
124
|
request.headers.add("accept", APPLICATION_JSON_STREAM)
|
@@ -30,66 +132,36 @@ module Async
|
|
30
132
|
end
|
31
133
|
end
|
32
134
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
@response = String.new
|
41
|
-
@value = {response: @response}
|
42
|
-
end
|
43
|
-
|
44
|
-
def read
|
45
|
-
return if @buffer.nil?
|
46
|
-
|
47
|
-
while true
|
48
|
-
if index = @buffer.index("\n", @offset)
|
49
|
-
line = @buffer.byteslice(@offset, index - @offset)
|
50
|
-
@buffer = @buffer.byteslice(index + 1, @buffer.bytesize - index - 1)
|
51
|
-
@offset = 0
|
52
|
-
|
53
|
-
return ::JSON.parse(line, symbolize_names: true)
|
54
|
-
end
|
55
|
-
|
56
|
-
if chunk = super
|
57
|
-
@buffer << chunk
|
58
|
-
else
|
59
|
-
return nil if @buffer.empty?
|
60
|
-
|
61
|
-
line = @buffer
|
62
|
-
@buffer = nil
|
63
|
-
@offset = 0
|
64
|
-
|
65
|
-
return ::JSON.parse(line, symbolize_names: true)
|
66
|
-
end
|
67
|
-
end
|
68
|
-
end
|
69
|
-
|
70
|
-
def each
|
71
|
-
super do |line|
|
72
|
-
token = line.delete(:response)
|
73
|
-
@response << token
|
74
|
-
@value.merge!(line)
|
75
|
-
|
76
|
-
yield token
|
77
|
-
end
|
78
|
-
end
|
135
|
+
# Selects the appropriate parser for the HTTP response.
|
136
|
+
# @parameter response [Protocol::HTTP::Response] The HTTP response object.
|
137
|
+
# @returns [Class] The parser class to use.
|
138
|
+
def parser_for(response)
|
139
|
+
content_type = response.headers["content-type"]
|
140
|
+
media_type = content_type.split(";").first
|
79
141
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
142
|
+
case media_type
|
143
|
+
when APPLICATION_JSON
|
144
|
+
return Async::REST::Wrapper::JSON::Parser
|
145
|
+
when APPLICATION_JSON_STREAM
|
146
|
+
return StreamingResponseParser
|
84
147
|
end
|
85
148
|
end
|
86
|
-
|
149
|
+
end
|
150
|
+
|
151
|
+
# Wraps chat-specific HTTP responses for the Ollama API, selecting the appropriate parser.
|
152
|
+
class ChatWrapper < Wrapper
|
153
|
+
# Selects the appropriate parser for the chat HTTP response.
|
154
|
+
# @parameter response [Protocol::HTTP::Response] The HTTP response object.
|
155
|
+
# @returns [Class] The parser class to use.
|
87
156
|
def parser_for(response)
|
88
|
-
|
157
|
+
content_type = response.headers["content-type"]
|
158
|
+
media_type = content_type.split(";").first
|
159
|
+
|
160
|
+
case media_type
|
89
161
|
when APPLICATION_JSON
|
90
162
|
return Async::REST::Wrapper::JSON::Parser
|
91
163
|
when APPLICATION_JSON_STREAM
|
92
|
-
return
|
164
|
+
return StreamingMessageParser
|
93
165
|
end
|
94
166
|
end
|
95
167
|
end
|
data/lib/async/ollama.rb
CHANGED
@@ -1,7 +1,16 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Released under the MIT License.
|
4
|
-
# Copyright, 2024, by Samuel Williams.
|
4
|
+
# Copyright, 2024-2025, by Samuel Williams.
|
5
5
|
|
6
6
|
require_relative "ollama/version"
|
7
7
|
require_relative "ollama/client"
|
8
|
+
|
9
|
+
require_relative "ollama/conversation"
|
10
|
+
|
11
|
+
# @namespace
|
12
|
+
module Async
|
13
|
+
# @namespace
|
14
|
+
module Ollama
|
15
|
+
end
|
16
|
+
end
|
data/license.md
CHANGED
data/readme.md
CHANGED
@@ -10,6 +10,15 @@ Please see the [project documentation](https://socketry.github.io/async-ollama/)
|
|
10
10
|
|
11
11
|
- [Getting Started](https://socketry.github.io/async-ollama/guides/getting-started/index) - This guide explains how to get started with the `async-ollama` gem.
|
12
12
|
|
13
|
+
## Releases
|
14
|
+
|
15
|
+
Please see the [project releases](https://socketry.github.io/async-ollama/releases/index) for all releases.
|
16
|
+
|
17
|
+
### v0.5.0
|
18
|
+
|
19
|
+
- Add `Async::Ollama::Conversation` for interactive real-time conversations. Includes support for summarization of chat history for large conversations.
|
20
|
+
- Add `Async::Ollama::Toolbox` for managing tool based interactions with models.
|
21
|
+
|
13
22
|
## Contributing
|
14
23
|
|
15
24
|
We welcome contributions to this project.
|
data/releases.md
ADDED
data.tar.gz.sig
CHANGED
Binary file
|
metadata
CHANGED
@@ -1,11 +1,10 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: async-ollama
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Samuel Williams
|
8
|
-
autorequire:
|
9
8
|
bindir: bin
|
10
9
|
cert_chain:
|
11
10
|
- |
|
@@ -37,7 +36,7 @@ cert_chain:
|
|
37
36
|
Q2K9NVun/S785AP05vKkXZEFYxqG6EW012U4oLcFl5MySFajYXRYbuUpH6AY+HP8
|
38
37
|
voD0MPg1DssDLKwXyt1eKD/+Fq0bFWhwVM/1XiAXL7lyYUyOq24KHgQ2Csg=
|
39
38
|
-----END CERTIFICATE-----
|
40
|
-
date:
|
39
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
41
40
|
dependencies:
|
42
41
|
- !ruby/object:Gem::Dependency
|
43
42
|
name: async
|
@@ -57,36 +56,38 @@ dependencies:
|
|
57
56
|
name: async-rest
|
58
57
|
requirement: !ruby/object:Gem::Requirement
|
59
58
|
requirements:
|
60
|
-
- - "
|
59
|
+
- - ">="
|
61
60
|
- !ruby/object:Gem::Version
|
62
|
-
version: '0
|
61
|
+
version: '0'
|
63
62
|
type: :runtime
|
64
63
|
prerelease: false
|
65
64
|
version_requirements: !ruby/object:Gem::Requirement
|
66
65
|
requirements:
|
67
|
-
- - "
|
66
|
+
- - ">="
|
68
67
|
- !ruby/object:Gem::Version
|
69
|
-
version: '0
|
70
|
-
description:
|
71
|
-
email:
|
68
|
+
version: '0'
|
72
69
|
executables: []
|
73
70
|
extensions: []
|
74
71
|
extra_rdoc_files: []
|
75
72
|
files:
|
76
73
|
- lib/async/ollama.rb
|
74
|
+
- lib/async/ollama/chat.rb
|
77
75
|
- lib/async/ollama/client.rb
|
76
|
+
- lib/async/ollama/conversation.rb
|
78
77
|
- lib/async/ollama/generate.rb
|
78
|
+
- lib/async/ollama/models.rb
|
79
|
+
- lib/async/ollama/toolbox.rb
|
79
80
|
- lib/async/ollama/version.rb
|
80
81
|
- lib/async/ollama/wrapper.rb
|
81
82
|
- license.md
|
82
83
|
- readme.md
|
84
|
+
- releases.md
|
83
85
|
homepage: https://github.com/socketry/async-ollama
|
84
86
|
licenses:
|
85
87
|
- MIT
|
86
88
|
metadata:
|
87
89
|
documentation_uri: https://socketry.github.io/async-ollama/
|
88
90
|
source_code_uri: https://github.com/socketry/async-ollama.git
|
89
|
-
post_install_message:
|
90
91
|
rdoc_options: []
|
91
92
|
require_paths:
|
92
93
|
- lib
|
@@ -94,15 +95,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
94
95
|
requirements:
|
95
96
|
- - ">="
|
96
97
|
- !ruby/object:Gem::Version
|
97
|
-
version: '3.
|
98
|
+
version: '3.2'
|
98
99
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
99
100
|
requirements:
|
100
101
|
- - ">="
|
101
102
|
- !ruby/object:Gem::Version
|
102
103
|
version: '0'
|
103
104
|
requirements: []
|
104
|
-
rubygems_version: 3.
|
105
|
-
signing_key:
|
105
|
+
rubygems_version: 3.6.7
|
106
106
|
specification_version: 4
|
107
107
|
summary: A asynchronous interface to the ollama chat service
|
108
108
|
test_files: []
|
metadata.gz.sig
CHANGED
Binary file
|