Skip to content

Commit 7088a6f

Browse files
committed
Refactor Ollama to use OpenAI's Native Gem Types
1 parent ef7d870 commit 7088a6f

10 files changed

Lines changed: 669 additions & 426 deletions

File tree

lib/active_agent/providers/ollama/chat/request.rb

Lines changed: 214 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,66 +1,245 @@
11
# frozen_string_literal: true
22

3-
require_relative "../../open_ai/chat/request"
4-
require_relative "requests/_types"
3+
require "delegate"
4+
require "json"
5+
require_relative "transforms"
56

67
module ActiveAgent
78
module Providers
89
module Ollama
910
module Chat
10-
# Ollama uses the same request structure as OpenAI's chat completion API
11-
# This class exists to allow for Ollama-specific customizations.
12-
class Request < OpenAI::Chat::Request
13-
# Messages array (required)
14-
attribute :messages, Requests::Messages::MessagesType.new
11+
# Wraps OpenAI gem's CompletionCreateParams with Ollama-specific extensions
12+
#
13+
# Delegates to OpenAI::Models::Chat::CompletionCreateParams for OpenAI-compatible
14+
# parameters while adding support for Ollama-specific features like format,
15+
# options, keep_alive, and raw mode.
16+
#
17+
# Ollama-specific parameters:
18+
# - format: Return response in JSON or as a JSON schema (String "json" or Hash)
19+
# - options: Additional model parameters (Hash of key-value pairs)
20+
# - keep_alive: Controls how long model stays in memory (String duration or Integer seconds)
21+
# - raw: If true, no formatting applied to prompt (Boolean)
22+
#
23+
# @example Basic usage
24+
# request = Request.new(
25+
# model: "llama2",
26+
# messages: [{role: "user", content: "Hello"}]
27+
# )
28+
#
29+
# @example With Ollama-specific features
30+
# request = Request.new(
31+
# model: "llama2",
32+
# messages: [{role: "user", content: "Hello"}],
33+
# format: "json",
34+
# options: {temperature: 0.7, num_predict: 100},
35+
# keep_alive: "5m"
36+
# )
37+
class Request < SimpleDelegator
38+
# Default parameter values
39+
DEFAULTS = {
40+
frequency_penalty: 0,
41+
logprobs: false,
42+
n: 1,
43+
parallel_tool_calls: true,
44+
presence_penalty: 0,
45+
temperature: 1,
46+
top_p: 1,
47+
raw: false,
48+
keep_alive: "5m"
49+
}.freeze
1550

16-
# Ollama-specific parameters
51+
# @return [Boolean, nil]
52+
attr_reader :stream
1753

18-
# Format: return response in json or as a JSON schema
19-
# Can be "json" or a JSON schema object
20-
attribute :format
54+
# @return [Hash] Ollama-specific parameters
55+
attr_reader :ollama_params
2156

22-
# Options: additional model parameters (temperature, num_predict, etc.)
23-
# Hash of key-value pairs for model-specific options
24-
attribute :options
57+
# Creates a new Ollama request
58+
#
59+
# @param params [Hash] request parameters
60+
# @option params [String] :model model identifier (required)
61+
# @option params [Array, String, Hash] :messages required conversation messages
62+
# @option params [String, Hash] :format JSON format ("json" or schema object)
63+
# @option params [Hash] :options model-specific options
64+
# @option params [String, Integer] :keep_alive memory duration
65+
# @option params [Boolean] :raw raw prompt mode
66+
# @raise [ArgumentError] when parameters are invalid
67+
def initialize(**params)
68+
# Extract stream flag
69+
@stream = params[:stream]
2570

26-
# Keep alive: controls how long the model stays loaded in memory
27-
# String duration (e.g., "5m", "1h") or integer in seconds
28-
# Default: "5m"
29-
attribute :keep_alive
71+
# Apply defaults
72+
params = apply_defaults(params)
3073

31-
# Raw: if true, no formatting will be applied to the prompt
32-
# You may use this if you are specifying a full templated prompt
33-
attribute :raw, :boolean, default: false
74+
# Normalize parameters and split into OpenAI vs Ollama-specific
75+
openai_params, @ollama_params = Transforms.normalize_params(params)
3476

35-
# Validations for Ollama-specific parameters
36-
validate :validate_format
37-
validate :validate_options_format
77+
# Validate Ollama-specific parameters
78+
validate_format(@ollama_params[:format]) if @ollama_params[:format]
79+
validate_options(@ollama_params[:options]) if @ollama_params[:options]
3880

39-
# Common Format Compatability
81+
# Create gem model with OpenAI-compatible params
82+
gem_model = ::OpenAI::Models::Chat::CompletionCreateParams.new(**openai_params)
83+
84+
# Delegate to the gem model
85+
super(gem_model)
86+
rescue ArgumentError => e
87+
raise ArgumentError, "Invalid Ollama request parameters: #{e.message}"
88+
end
89+
90+
# Serializes request for API submission
91+
#
92+
# Merges OpenAI-compatible parameters with Ollama-specific extensions.
93+
#
94+
# @return [Hash] cleaned request hash
95+
def serialize
96+
# Get OpenAI params from gem model
97+
openai_hash = Transforms.gem_to_hash(__getobj__)
98+
99+
# Merge with Ollama-specific params
100+
Transforms.cleanup_serialized_request(openai_hash, @ollama_params, DEFAULTS, __getobj__)
101+
end
102+
103+
# @return [Array<Hash>, nil]
104+
def messages
105+
__getobj__.instance_variable_get(:@data)[:messages]
106+
end
107+
108+
# Sets messages with normalization
109+
#
110+
# Merges new messages with existing ones for compatibility.
111+
#
112+
# @param value [Array, String, Hash]
113+
# @return [void]
40114
def messages=(value)
41-
case value
42-
when Array
43-
super((messages || []) | value)
44-
else
45-
super((messages || []) | [ value ])
46-
end
115+
normalized_value = Transforms.normalize_messages(value)
116+
current_messages = messages || []
117+
118+
# Merge behavior for Ollama compatibility
119+
merged = current_messages | Array(normalized_value)
120+
__getobj__.instance_variable_get(:@data)[:messages] = merged
121+
end
122+
123+
# Alias for messages (common format compatibility)
124+
#
125+
# @return [Array<Hash>, nil]
126+
def message
127+
messages
128+
end
129+
130+
# @param value [Array, String, Hash]
131+
# @return [void]
132+
def message=(value)
133+
self.messages = value
134+
end
135+
136+
# Sets instructions as developer messages
137+
#
138+
# Prepends developer messages to the messages array.
139+
#
140+
# @param values [Array<String>, String]
141+
# @return [void]
142+
def instructions=(*values)
143+
instructions_messages = Transforms.normalize_instructions(values.flatten)
144+
current_messages = messages || []
145+
self.messages = instructions_messages + current_messages
146+
end
147+
148+
# Accessor for Ollama format parameter
149+
#
150+
# @return [String, Hash, nil]
151+
def format
152+
@ollama_params[:format]
153+
end
154+
155+
# Sets format parameter
156+
#
157+
# @param value [String, Hash]
158+
# @return [void]
159+
def format=(value)
160+
validate_format(value)
161+
@ollama_params[:format] = value
162+
end
163+
164+
# Accessor for Ollama options parameter
165+
#
166+
# @return [Hash, nil]
167+
def options
168+
@ollama_params[:options]
169+
end
170+
171+
# Sets options parameter
172+
#
173+
# @param value [Hash]
174+
# @return [void]
175+
def options=(value)
176+
validate_options(value)
177+
@ollama_params[:options] = value
178+
end
179+
180+
# Accessor for keep_alive parameter
181+
#
182+
# @return [String, Integer, nil]
183+
def keep_alive
184+
@ollama_params[:keep_alive] || DEFAULTS[:keep_alive]
185+
end
186+
187+
# Sets keep_alive parameter
188+
#
189+
# @param value [String, Integer]
190+
# @return [void]
191+
def keep_alive=(value)
192+
@ollama_params[:keep_alive] = value
193+
end
194+
195+
# Accessor for raw parameter
196+
#
197+
# @return [Boolean]
198+
def raw
199+
@ollama_params[:raw] || DEFAULTS[:raw]
200+
end
201+
202+
# Sets raw parameter
203+
#
204+
# @param value [Boolean]
205+
# @return [void]
206+
def raw=(value)
207+
@ollama_params[:raw] = value
47208
end
48209

49210
private
50211

51-
def validate_format
212+
# @api private
213+
# @param params [Hash]
214+
# @return [Hash]
215+
def apply_defaults(params)
216+
# Apply defaults
217+
DEFAULTS.each do |key, value|
218+
params[key] = value unless params.key?(key)
219+
end
220+
221+
params
222+
end
223+
224+
# @api private
225+
# @param format [String, Hash, nil]
226+
# @raise [ArgumentError]
227+
def validate_format(format)
52228
return if format.nil?
53229
return if format == "json"
54230
return if format.is_a?(Hash) # JSON schema object
55231

56-
errors.add(:format, "must be 'json' or a JSON schema object")
232+
raise ArgumentError, "format must be 'json' or a JSON schema object"
57233
end
58234

59-
def validate_options_format
235+
# @api private
236+
# @param options [Hash, nil]
237+
# @raise [ArgumentError]
238+
def validate_options(options)
60239
return if options.nil?
61240

62241
unless options.is_a?(Hash)
63-
errors.add(:options, "must be a hash of model parameters")
242+
raise ArgumentError, "options must be a hash of model parameters"
64243
end
65244
end
66245
end

lib/active_agent/providers/ollama/chat/requests/_types.rb

Lines changed: 0 additions & 3 deletions
This file was deleted.

0 commit comments

Comments
 (0)