Module: RubyLLM::Providers::Mistral::Chat

Included in:
RubyLLM::Providers::Mistral
Defined in:
lib/ruby_llm/providers/mistral/chat.rb

Overview

Chat methods for Mistral API

Class Method Summary collapse

Class Method Details

.adjustable_reasoning_model?(model_id) ⇒ Boolean

Returns:

  • (Boolean)


103
104
105
# File 'lib/ruby_llm/providers/mistral/chat.rb', line 103

def adjustable_reasoning_model?(model_id)
  model_id.to_s.match?(/\Amistral-(?:small-latest|medium-(?:3(?:[.-]5)?|latest))\z/)
end

.append_formatted_content(content_blocks, formatted_content) ⇒ Object



123
124
125
126
127
128
129
# File 'lib/ruby_llm/providers/mistral/chat.rb', line 123

def append_formatted_content(content_blocks, formatted_content)
  if formatted_content.is_a?(Array)
    content_blocks.concat(formatted_content)
  elsif formatted_content
    content_blocks << { type: 'text', text: formatted_content }
  end
end

.build_thinking_blocks(thinking) ⇒ Object



107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# File 'lib/ruby_llm/providers/mistral/chat.rb', line 107

def build_thinking_blocks(thinking)
  return [] unless thinking

  if thinking.text
    [{
      type: 'thinking',
      thinking: [{ type: 'text', text: thinking.text }],
      signature: thinking.signature
    }.compact]
  elsif thinking.signature
    [{ type: 'thinking', signature: thinking.signature }]
  else
    []
  end
end

.build_tool_choice(tool_choice) ⇒ Object

rubocop:enable Metrics/ParameterLists



36
37
38
39
40
# File 'lib/ruby_llm/providers/mistral/chat.rb', line 36

def build_tool_choice(tool_choice)
  return 'any' if tool_choice == :required

  OpenAI::Tools.build_tool_choice(tool_choice)
end

.configure_native_reasoning_payload(payload, thinking) ⇒ Object



87
88
89
90
# File 'lib/ruby_llm/providers/mistral/chat.rb', line 87

def configure_native_reasoning_payload(payload, thinking)
  payload.delete(:reasoning_effort)
  payload[:prompt_mode] = thinking.effort == 'none' ? nil : 'reasoning'
end

.configure_thinking_payload(payload, model, thinking) ⇒ Object



74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/ruby_llm/providers/mistral/chat.rb', line 74

def configure_thinking_payload(payload, model, thinking)
  return unless thinking&.enabled?

  if native_reasoning_model?(model.id)
    configure_native_reasoning_payload(payload, thinking)
  elsif adjustable_reasoning_model?(model.id)
    payload[:reasoning_effort] = reasoning_effort_for(thinking)
  else
    payload.delete(:reasoning_effort)
    warn_on_unsupported_thinking(model, thinking)
  end
end

.format_content_with_thinking(msg) ⇒ Object



54
55
56
57
58
59
60
61
62
# File 'lib/ruby_llm/providers/mistral/chat.rb', line 54

def format_content_with_thinking(msg)
  formatted_content = OpenAI::Media.format_content(msg.content)
  return formatted_content unless msg.role == :assistant && msg.thinking

  content_blocks = build_thinking_blocks(msg.thinking)
  append_formatted_content(content_blocks, formatted_content)

  content_blocks
end

.format_messages(messages) ⇒ Object



14
15
16
17
18
19
20
21
22
23
# File 'lib/ruby_llm/providers/mistral/chat.rb', line 14

def format_messages(messages)
  messages.map do |msg|
    {
      role: format_role(msg.role),
      content: format_content_with_thinking(msg),
      tool_calls: OpenAI::Tools.format_tool_calls(msg.tool_calls),
      tool_call_id: msg.tool_call_id
    }.compact
  end
end

.format_role(role) ⇒ Object



10
11
12
# File 'lib/ruby_llm/providers/mistral/chat.rb', line 10

def format_role(role)
  role.to_s
end

.native_reasoning_model?(model_id) ⇒ Boolean

Returns:

  • (Boolean)


99
100
101
# File 'lib/ruby_llm/providers/mistral/chat.rb', line 99

def native_reasoning_model?(model_id)
  model_id.to_s.include?('magistral')
end

.normalize_required_tool_choice(payload) ⇒ Object



42
43
44
45
46
47
48
49
50
51
52
# File 'lib/ruby_llm/providers/mistral/chat.rb', line 42

def normalize_required_tool_choice(payload)
  return unless payload[:tool_choice] == 'any' && Array(payload[:tools]).one?

  function_name = payload.dig(:tools, 0, :function, :name)
  return unless function_name

  payload[:tool_choice] = {
    type: 'function',
    function: { name: function_name }
  }
end

.reasoning_effort_for(thinking) ⇒ Object



92
93
94
95
96
97
# File 'lib/ruby_llm/providers/mistral/chat.rb', line 92

def reasoning_effort_for(thinking)
  effort = thinking.respond_to?(:effort) ? thinking.effort : nil
  return effort if %w[high none].include?(effort)

  'high'
end

.render_payload(messages, tools:, temperature:, model:, stream: false, schema: nil, thinking: nil, tool_prefs: nil) ⇒ Object

rubocop:disable Metrics/ParameterLists



26
27
28
29
30
31
32
33
# File 'lib/ruby_llm/providers/mistral/chat.rb', line 26

def render_payload(messages, tools:, temperature:, model:, stream: false,
                   schema: nil, thinking: nil, tool_prefs: nil)
  payload = super
  payload.delete(:stream_options)
  configure_thinking_payload(payload, model, thinking)
  normalize_required_tool_choice(payload)
  payload
end

.warn_on_unsupported_thinking(model, thinking) ⇒ Object



64
65
66
67
68
69
70
71
72
# File 'lib/ruby_llm/providers/mistral/chat.rb', line 64

def warn_on_unsupported_thinking(model, thinking)
  return unless thinking&.enabled?
  return if native_reasoning_model?(model.id) || adjustable_reasoning_model?(model.id)

  RubyLLM.logger.warn(
    'Mistral thinking is only supported on Magistral and adjustable-reasoning models. ' \
    "Ignoring thinking settings for #{model.id}."
  )
end