Module: Ollama::Client::Chat

Included in:
Ollama::Client
Defined in:
lib/ollama/client/chat.rb

Overview

Chat completion endpoint — the primary method for multi-turn conversations

Instance Method Summary collapse

Instance Method Details

#chat(messages:, model: nil, format: nil, tools: nil, stream: nil, think: nil, keep_alive: nil, options: nil, logprobs: nil, top_logprobs: nil, hooks: {}, profile: :auto, inputs: nil) ⇒ Ollama::Response

rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/ParameterLists, Metrics/AbcSize

Parameters:

  • messages (Array<Hash>)

    Chat history, each with :role and :content (required)

  • model (String, nil) (defaults to: nil)

    Model name override

  • format (Hash, String, nil) (defaults to: nil)

    “json” or JSON Schema object for structured output

  • tools (Array<Hash>, nil) (defaults to: nil)

    Function tools the model may call

  • stream (Boolean, nil) (defaults to: nil)

    Stream partial responses (default: determined by hooks)

  • think (Boolean, String, nil) (defaults to: nil)

    Enable thinking output (true/false/“high”/“medium”/“low”)

  • keep_alive (String, nil) (defaults to: nil)

    Model keep-alive duration (e.g. “5m”, “0”)

  • options (Hash, nil) (defaults to: nil)

    Runtime options (temperature, top_p, num_ctx, etc.)

  • logprobs (Boolean, nil) (defaults to: nil)

    Return log probabilities

  • top_logprobs (Integer, nil) (defaults to: nil)

    Number of top logprobs to return

  • profile (:auto, false, ModelProfile) (defaults to: :auto)

    Capability profile for model-aware behavior

  • inputs (Array<Hash>, nil) (defaults to: nil)

    Typed multimodal inputs (overrides last user message)

  • hooks (Hash) (defaults to: {})

    Streaming callbacks: :on_token ->(text, logprobs=nil) — final-answer token :on_thought ->(text) — reasoning/thinking token :on_tool_call ->(tool_call_hash) — tool call ready :on_error ->(error) — stream or connection error :on_complete -> — stream finished

Returns:

  • (Ollama::Response)

    Response wrapper with message, tool_calls, timing, etc.



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
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/ollama/client/chat.rb', line 29

def chat(messages:, model: nil, format: nil, tools: nil, stream: nil,
         think: nil, keep_alive: nil, options: nil, logprobs: nil,
         top_logprobs: nil, hooks: {}, profile: :auto, inputs: nil)
  raise ArgumentError, "messages is required" if messages.nil? || messages.empty?

  target_model = model || @config.model
  active_profile = resolve_profile(target_model, profile)
  adapter = PromptAdapters.for(active_profile) if active_profile

  # Apply multimodal inputs: build typed message and append to history
  messages = apply_inputs(messages, inputs, active_profile) if inputs

  # Apply prompt adapter (e.g. Gemma 4 prepends the family think tag to the system prompt)
  adapted_messages = adapter ? adapter.adapt_messages(messages, think: !think.nil?) : messages

  # Resolve think flag: adapter may handle it via prompt tag instead of API flag
  effective_think = resolve_think_flag(think, adapter)

  chat_uri = URI("#{@config.base_url}/api/chat")
  req = Net::HTTP::Post.new(chat_uri)
  req["Content-Type"] = "application/json"

  stream_enabled = stream.nil? ? hooks_present?(hooks) : stream

  body = { model: target_model, messages: adapted_messages, stream: stream_enabled }
  body[:format]      = format if format
  body[:tools]       = tools if tools
  body[:think]       = effective_think unless effective_think.nil?
  body[:keep_alive]  = keep_alive if keep_alive
  body[:logprobs]    = logprobs unless logprobs.nil?
  body[:top_logprobs] = top_logprobs if top_logprobs
  body[:options] = build_options_with_profile(options, active_profile)

  req.body = body.to_json
  @config.apply_auth_to(req)
  response_data = nil

  begin
    Net::HTTP.start(chat_uri.hostname, chat_uri.port,
                    **@config.http_connection_options(chat_uri)) do |h|
      h.request(req) do |res|
        handle_http_error(res, requested_model: target_model) unless res.is_a?(Net::HTTPSuccess)

        response_data = if stream_enabled
                          ChatStreamProcessor.new(hooks).call(res)
                        else
                          JSON.parse(res.body)
                        end
      end
    end
  rescue Net::ReadTimeout, Net::OpenTimeout => e
    hooks[:on_error]&.call(e)
    raise TimeoutError, "Request timed out after #{@config.timeout}s"
  rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH, SocketError => e
    hooks[:on_error]&.call(e)
    raise Error, "Connection failed: #{e.message}"
  rescue Error => e
    hooks[:on_error]&.call(e)
    raise e
  end

  emit_response_hook(response_data.is_a?(Hash) ? response_data.to_json : response_data,
                     endpoint: "/api/chat", model: target_model)

  Response.new(response_data)
rescue JSON::ParserError => e
  raise InvalidJSONError, "Failed to parse chat response: #{e.message}"
end