Class: Spectre::Ollama::Completions
- Inherits:
-
Object
- Object
- Spectre::Ollama::Completions
- Defined in:
- lib/spectre/ollama/completions.rb
Constant Summary collapse
- API_PATH =
'api/chat'- DEFAULT_MODEL =
'llama3.1:8b'- DEFAULT_TIMEOUT =
60
Class Method Summary collapse
-
.create(messages:, model: DEFAULT_MODEL, json_schema: nil, tools: nil, **args) ⇒ Hash
Class method to generate a completion based on user messages and optional tools.
-
.generate_body(messages, model, json_schema, tools, options) ⇒ Hash
Helper method to generate the request body.
-
.handle_response(response) ⇒ Hash
Handles the API response, raising errors for specific cases and returning structured content otherwise.
-
.validate_messages!(messages) ⇒ Object
Validate the structure and content of the messages array.
Class Method Details
.create(messages:, model: DEFAULT_MODEL, json_schema: nil, tools: nil, **args) ⇒ Hash
Class method to generate a completion based on user messages and optional tools
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
# File 'lib/spectre/ollama/completions.rb', line 27 def self.create(messages:, model: DEFAULT_MODEL, json_schema: nil, tools: nil, **args) api_host = Spectre.ollama_configuration.host api_key = Spectre.ollama_configuration.api_key raise HostNotConfiguredError, "Host is not configured" unless api_host raise APIKeyNotConfiguredError, "API key is not configured" unless api_key () path = args[:path] || API_PATH uri = URI.join(api_host, path) http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = true if uri.scheme == 'https' http.read_timeout = args.fetch(:read_timeout, DEFAULT_TIMEOUT) http.open_timeout = args.fetch(:open_timeout, DEFAULT_TIMEOUT) request = Net::HTTP::Post.new(uri.path, { 'Content-Type' => 'application/json', 'Authorization' => "Bearer #{api_key}" }) # Forward extra top-level args (like temperature, max_tokens) into body[:options], # excluding control/network keys and the request path override. forwarded = args.reject { |k, _| [:read_timeout, :open_timeout, :path].include?(k) } = nil if forwarded && !forwarded.empty? = forwarded.transform_keys(&:to_sym) end request.body = generate_body(, model, json_schema, tools, ).to_json response = http.request(request) unless response.is_a?(Net::HTTPSuccess) raise "Ollama API Error: #{response.code} - #{response.message}: #{response.body}" end parsed_response = JSON.parse(response.body) handle_response(parsed_response) rescue JSON::ParserError => e raise "JSON Parse Error: #{e.message}" end |
.generate_body(messages, model, json_schema, tools, options) ⇒ Hash
Helper method to generate the request body
97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 |
# File 'lib/spectre/ollama/completions.rb', line 97 def self.generate_body(, model, json_schema, tools, ) body = { model: model, stream: false, messages: } # Extract schema if json_schema follows OpenAI's structure if json_schema.is_a?(Hash) && json_schema.key?(:schema) body[:format] = json_schema[:schema] # Use only the "schema" key elsif json_schema.is_a?(Hash) body[:format] = json_schema # Use the schema as-is if it doesn't follow OpenAI's structure end body[:tools] = tools if tools # Add the tools to the request body if provided body[:options] = if body end |
.handle_response(response) ⇒ Hash
Handles the API response, raising errors for specific cases and returning structured content otherwise
121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 |
# File 'lib/spectre/ollama/completions.rb', line 121 def self.handle_response(response) = response.dig('message') finish_reason = response.dig('done_reason') done = response.dig('done') # Check if the model made a function call if ['tool_calls'] && !['tool_calls'].empty? return { tool_calls: ['tool_calls'], content: ['content'] } end # If the response finished normally, return the content if done return { content: ['content'] } end # Handle unexpected finish reasons raise "Unexpected finish_reason: #{finish_reason}, done: #{done}, message: #{message}" end |
.validate_messages!(messages) ⇒ Object
Validate the structure and content of the messages array.
75 76 77 78 79 80 81 82 83 84 85 86 87 |
# File 'lib/spectre/ollama/completions.rb', line 75 def self.() # Check if messages is an array of hashes. # This ensures that the input is in the correct format for message processing. unless .is_a?(Array) && .all? { |msg| msg.is_a?(Hash) } raise ArgumentError, "Messages must be an array of message hashes." end # Check if the array is empty. # This prevents requests with no messages, which would be invalid. if .empty? raise ArgumentError, "Messages cannot be empty." end end |