Module: RubyLLM::Protocols::Mistral::Conversations::Chat
- Defined in:
- lib/ruby_llm/protocols/mistral/conversations/chat.rb
Overview
:nodoc:
Class Method Summary collapse
- .completion_url ⇒ Object
- .format_conversation_message(message) ⇒ Object
- .format_entries(messages) ⇒ Object
- .normalize_conversation_choice(options) ⇒ Object
- .parse_completion_body(data, raw:) ⇒ Object
- .parse_conversation_calls(output, raw:) ⇒ Object
- .parse_conversation_steps(output) ⇒ Object
- .parse_conversation_usage(usage) ⇒ Object
- .pending_conversation_call?(entry) ⇒ Boolean
- .render ⇒ Object
- .render_payload(messages, tools:, temperature:, model:, stream: false, max_output_tokens: nil, schema: nil, thinking: nil, tool_prefs: nil) ⇒ Object
- .replay_conversation_entry(entry, index) ⇒ Object
- .replay_conversation_execution(entry, index) ⇒ Object
Class Method Details
.completion_url ⇒ Object
10 11 12 |
# File 'lib/ruby_llm/protocols/mistral/conversations/chat.rb', line 10 def completion_url 'conversations' end |
.format_conversation_message(message) ⇒ Object
90 91 92 93 94 95 96 97 98 99 100 101 102 103 |
# File 'lib/ruby_llm/protocols/mistral/conversations/chat.rb', line 90 def () entries = [] if .content || ..any? entries << { type: 'message.input', role: .role.to_s, content: () } end .tool_calls&.each_value do |call| entries << { type: 'function.call', tool_call_id: call.id, name: call.name, arguments: JSON.generate(call.arguments) } end entries end |
.format_entries(messages) ⇒ Object
50 51 52 53 54 55 56 57 58 59 60 61 |
# File 'lib/ruby_llm/protocols/mistral/conversations/chat.rb', line 50 def format_entries() entries = .reject { || .role == :system }.flat_map do || if .raw_content Array(.raw_content) elsif .tool_result? [{ type: 'function.result', tool_call_id: .tool_call_id, result: .content.to_s }] else () end end entries.each_with_index.flat_map { |entry, index| replay_conversation_entry(entry, index) } end |
.normalize_conversation_choice(options) ⇒ Object
41 42 43 44 45 46 47 48 |
# File 'lib/ruby_llm/protocols/mistral/conversations/chat.rb', line 41 def normalize_conversation_choice() choice = [:tool_choice] if choice.is_a?(Hash) raise ArgumentError, 'Mistral Conversations supports :auto, :none, or :required for tool choice' end [:tool_choice] = 'any' if choice == 'required' end |
.parse_completion_body(data, raw:) ⇒ Object
105 106 107 108 109 110 111 112 113 114 115 116 117 118 |
# File 'lib/ruby_llm/protocols/mistral/conversations/chat.rb', line 105 def parse_completion_body(data, raw:) output = data.fetch('outputs') content = parse_conversation_content(output) calls = parse_conversation_calls(output, raw:) response_model = output.filter_map { |entry| entry['model'] }.last || @model&.id Message.new( role: :assistant, content: content[:text], attachments: content[:attachments], citations: content[:citations], thinking: Thinking.build(text: content[:thinking]), tool_calls: calls, server_tool_calls: parse_conversation_steps(output), raw_content: output, model: response_model, finish_reason: calls.empty? ? :stop : :tool_calls, raw: raw, **parse_conversation_usage(data['usage'] || {}) ) end |
.parse_conversation_calls(output, raw:) ⇒ Object
120 121 122 123 124 125 126 127 128 129 130 131 132 133 |
# File 'lib/ruby_llm/protocols/mistral/conversations/chat.rb', line 120 def parse_conversation_calls(output, raw:) if output.any? { |entry| entry['type'] == 'function.call' && entry['confirmation_status'] == 'pending' } raise Error.new('Mistral returned a hosted tool confirmation that requires provider conversation storage', response: raw) end output.select { |entry| pending_conversation_call?(entry) }.to_h do |entry| call = parse_tool_calls([ { 'id' => entry['tool_call_id'], 'type' => 'function', 'function' => entry.slice('name', 'arguments') } ], response: raw, finish_reason: :tool_calls).values.first [call.id, call] end end |
.parse_conversation_steps(output) ⇒ Object
139 140 141 142 143 144 145 146 147 |
# File 'lib/ruby_llm/protocols/mistral/conversations/chat.rb', line 139 def parse_conversation_steps(output) output.filter_map do |entry| next unless entry['type'] == 'tool.execution' || (entry['type'] == 'function.call' && !pending_conversation_call?(entry)) ServerToolCall.new(type: entry['type'], name: entry['name'], id: entry['id'], input: entry['arguments'], result: entry['info'], raw: entry) end end |
.parse_conversation_usage(usage) ⇒ Object
149 150 151 152 153 154 155 |
# File 'lib/ruby_llm/protocols/mistral/conversations/chat.rb', line 149 def parse_conversation_usage(usage) input = usage['prompt_tokens'] && (usage['prompt_tokens'] + usage.fetch('connector_tokens', 0).to_i) { input_tokens: input, output_tokens: usage['completion_tokens'], server_tool_use: usage['connectors'] } end |
.pending_conversation_call?(entry) ⇒ Boolean
135 136 137 |
# File 'lib/ruby_llm/protocols/mistral/conversations/chat.rb', line 135 def pending_conversation_call?(entry) entry['type'] == 'function.call' && entry['confirmation_status'].nil? end |
.render ⇒ Object
14 15 16 17 18 19 20 21 22 |
# File 'lib/ruby_llm/protocols/mistral/conversations/chat.rb', line 14 def render(...) payload = super payload[:tools] = Support::Utils.deep_stringify_keys(payload[:tools]).uniq if payload[:tools].any? { |tool| Array(tool.dig('tool_configuration', 'requires_confirmation')).any? } raise ArgumentError, 'Mistral hosted tool confirmations require provider conversation storage' end payload end |
.render_payload(messages, tools:, temperature:, model:, stream: false, max_output_tokens: nil, schema: nil, thinking: nil, tool_prefs: nil) ⇒ Object
24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
# File 'lib/ruby_llm/protocols/mistral/conversations/chat.rb', line 24 def render_payload(, tools:, temperature:, model:, stream: false, max_output_tokens: nil, schema: nil, thinking: nil, tool_prefs: nil, **) = super(, tools:, temperature:, model:, stream:, max_output_tokens:, schema:, thinking:, citations: false, caching: nil, tool_prefs:) normalize_conversation_choice() { model: model.id, inputs: format_entries(), instructions: .select { || .role == :system }.map(&:content).join("\n\n"), completion_args: .slice(:temperature, :max_tokens, :response_format, :reasoning_effort, :tool_choice), tools: .fetch(:tools, []), store: false, stream: stream } end |
.replay_conversation_entry(entry, index) ⇒ Object
63 64 65 66 67 68 69 70 71 72 73 74 75 |
# File 'lib/ruby_llm/protocols/mistral/conversations/chat.rb', line 63 def replay_conversation_entry(entry, index) return replay_conversation_execution(entry, index) if entry['type'] == 'tool.execution' result = entry.except('id', 'object', 'created_at', 'completed_at') if entry['type'] == 'message.output' && entry['content'].is_a?(Array) result['content'] = entry['content'].map do |part| next part unless part['type'] == 'tool_reference' { 'type' => 'text', 'text' => "[#{part['title']}](#{part['url']})" } end end [result] end |
.replay_conversation_execution(entry, index) ⇒ Object
77 78 79 80 81 82 83 84 85 86 87 88 |
# File 'lib/ruby_llm/protocols/mistral/conversations/chat.rb', line 77 def replay_conversation_execution(entry, index) identity = entry['id'] || "#{index}:#{JSON.generate(entry)}" id = entry['tool_call_id'] || Digest::SHA256.hexdigest(identity)[0, 9] info = entry['info'] result = info.is_a?(Hash) && info.key?('result') ? info['result'] : info [ { 'type' => 'function.call', 'tool_call_id' => id, 'name' => entry['function'] || entry['name'], 'arguments' => entry['arguments'] }, { 'type' => 'function.result', 'tool_call_id' => id, 'result' => result.is_a?(String) ? result : JSON.generate(result) } ] end |