Module: RubyLLM::Protocols::Mistral::MultiCompletion
- Includes:
- Content
- Included in:
- RubyLLM::Providers::Mistral::ChatCompletions
- Defined in:
- lib/ruby_llm/protocols/mistral/multi_completion.rb
Overview
:nodoc:
Constant Summary collapse
- SERVER_TOOL_ALIASES =
{ image_generation: { tool: { type: 'image_generation' } }, mcp: { tool: { type: 'connector' } } }.freeze
Class Method Summary collapse
- .append_multi_calls(entry, calls) ⇒ Object
- .append_multi_content(entry, content) ⇒ Object
- .build_multi_chunk(data) ⇒ Object
- .format_message_group(group, **options) ⇒ Object
- .multi_content(messages) ⇒ Object
- .multi_messages ⇒ Object
- .multi_pending_calls(calls, results, raw:) ⇒ Object
- .multi_response ⇒ Object
- .multi_usage ⇒ Object
- .parse_completion_body(data, raw:) ⇒ Object
- .parse_multi_message(data, messages, raw:) ⇒ Object
- .parse_multi_steps(calls, results) ⇒ Object
- .server_tool_aliases ⇒ Object
- .stream_response(payload, additional_headers = {}, &block) ⇒ Object
Methods included from Content
parse_conversation_citation, parse_conversation_content, parse_conversation_file, parse_conversation_parts
Class Method Details
.append_multi_calls(entry, calls) ⇒ Object
123 124 125 126 127 128 129 130 |
# File 'lib/ruby_llm/protocols/mistral/multi_completion.rb', line 123 def append_multi_calls(entry, calls) Array(calls).each do |call| target = entry['tool_calls'][call.fetch('index', 0)] ||= { 'function' => { 'arguments' => +'' } } target.merge!(call.slice('id', 'type', 'metadata')) target['function']['name'] = call.dig('function', 'name') if call.dig('function', 'name') target['function']['arguments'] << call.dig('function', 'arguments').to_s end end |
.append_multi_content(entry, content) ⇒ Object
113 114 115 116 117 118 119 120 121 |
# File 'lib/ruby_llm/protocols/mistral/multi_completion.rb', line 113 def append_multi_content(entry, content) return if content.nil? if content.is_a?(String) entry['content'] << { 'type' => 'text', 'text' => content } else entry['content'].concat(content) end end |
.build_multi_chunk(data) ⇒ Object
97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 |
# File 'lib/ruby_llm/protocols/mistral/multi_completion.rb', line 97 def build_multi_chunk(data) @multi_model = data['model'] @multi_usage[data['id']] = data['usage'] if data['usage'] choice = data.dig('choices', 0) || {} delta = choice['delta'] || {} index = delta.fetch('index', 0) @multi_finish_reason = nil unless @multi_messages.key?(index) entry = @multi_messages[index] ||= { 'content' => [], 'tool_calls' => [] } entry.merge!(delta.slice('role', 'tool_call_id', 'metadata')) append_multi_content(entry, delta['content']) append_multi_calls(entry, delta['tool_calls']) @multi_finish_reason = choice['finish_reason'] if choice['finish_reason'] content, thinking = extract_content_and_thinking(delta['content']) if entry['role'] == 'assistant' Chunk.new(role: :assistant, content: content, thinking: Thinking.build(text: thinking), model: data['model']) end |
.format_message_group(group, **options) ⇒ Object
20 21 22 23 24 25 26 |
# File 'lib/ruby_llm/protocols/mistral/multi_completion.rb', line 20 def (group, **) content = group.first.raw_content return super unless group.one? && content.is_a?(Array) && content.all? { |entry| entry.is_a?(Hash) && entry['role'] } content.map { |entry| entry.except('index') } end |
.multi_content(messages) ⇒ Object
35 36 37 38 39 40 |
# File 'lib/ruby_llm/protocols/mistral/multi_completion.rb', line 35 def multi_content() output = .filter_map do || .merge('type' => 'message.output') if ['role'] == 'assistant' end parse_conversation_content(output) end |
.multi_messages ⇒ Object
148 149 150 151 152 153 154 155 156 |
# File 'lib/ruby_llm/protocols/mistral/multi_completion.rb', line 148 def @multi_messages.sort.map do |_, | = .reject { |key, value| key == 'tool_calls' && value.empty? } if ['role'] == 'tool' && ['content'].all? { |part| part['type'] == 'text' } ['content'] = ['content'].map { |part| part['text'] }.join end end end |
.multi_pending_calls(calls, results, raw:) ⇒ Object
42 43 44 45 46 47 48 49 |
# File 'lib/ruby_llm/protocols/mistral/multi_completion.rb', line 42 def multi_pending_calls(calls, results, raw:) pending = calls.reject { |call| results.key?(call['id']) } if pending.any? { |call| call.dig('metadata', 'tool_type') || call.dig('metadata', 'integration_id') } raise Error.new('Mistral returned an unfinished hosted tool call on Chat Completions', response: raw) end pending end |
.multi_response ⇒ Object
132 133 134 135 |
# File 'lib/ruby_llm/protocols/mistral/multi_completion.rb', line 132 def multi_response { 'model' => @multi_model, 'usage' => multi_usage, 'choices' => [{ 'messages' => , 'finish_reason' => @multi_finish_reason }] } end |
.multi_usage ⇒ Object
137 138 139 140 141 142 143 144 145 146 |
# File 'lib/ruby_llm/protocols/mistral/multi_completion.rb', line 137 def multi_usage usages = @multi_usage.values usage = %w[prompt_tokens completion_tokens total_tokens].to_h do |key| [key, usages.sum { |value| value[key].to_i }] end usage['prompt_tokens_details'] = { 'cached_tokens' => usages.sum do |value| value.dig('prompt_tokens_details', 'cached_tokens').to_i end } usage end |
.parse_completion_body(data, raw:) ⇒ Object
28 29 30 31 32 33 |
# File 'lib/ruby_llm/protocols/mistral/multi_completion.rb', line 28 def parse_completion_body(data, raw:) = data.dig('choices', 0, 'messages') return super unless .is_a?(Array) (data, , raw:) end |
.parse_multi_message(data, messages, raw:) ⇒ Object
51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
# File 'lib/ruby_llm/protocols/mistral/multi_completion.rb', line 51 def (data, , raw:) content = multi_content() results = .filter_map do || [['tool_call_id'], ] if ['role'] == 'tool' end.to_h calls = .flat_map { || Array(['tool_calls']) } pending = multi_pending_calls(calls, results, raw:) usage = data['usage'] || {} Message.new(role: :assistant, content: content[:text], thinking: Thinking.build(text: content[:thinking]), attachments: content[:attachments], citations: content[:citations], tool_calls: parse_tool_calls(pending, response: raw, finish_reason: :tool_calls), server_tool_calls: parse_multi_steps(calls, results), raw_content: , input_tokens: input_tokens(usage), output_tokens: output_tokens(usage), cache_read_tokens: cache_read_tokens(usage), model: data['model'], raw: raw, finish_reason: normalize_finish_reason(data.dig('choices', 0, 'finish_reason'))) end |
.parse_multi_steps(calls, results) ⇒ Object
68 69 70 71 72 73 74 75 76 |
# File 'lib/ruby_llm/protocols/mistral/multi_completion.rb', line 68 def parse_multi_steps(calls, results) calls.filter_map do |call| result = results[call['id']] next unless result ServerToolCall.new(type: result['role'], id: call['id'], name: call.dig('function', 'name'), input: call.dig('function', 'arguments'), result: result['content'], raw: result) end end |
.server_tool_aliases ⇒ Object
16 17 18 |
# File 'lib/ruby_llm/protocols/mistral/multi_completion.rb', line 16 def server_tool_aliases SERVER_TOOL_ALIASES end |
.stream_response(payload, additional_headers = {}, &block) ⇒ Object
78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 |
# File 'lib/ruby_llm/protocols/mistral/multi_completion.rb', line 78 def stream_response(payload, additional_headers = {}, &block) return super unless Array(payload[:tools]).any? { |tool| (tool[:type] || tool['type']) != 'function' } @multi_messages = {} @multi_usage = {} @multi_finish_reason = nil response = stream_events(completion_url, payload, additional_headers) do |data| block.call(build_multi_chunk(data)) end raise Error.new('Mistral tool stream ended before completion', response:) unless @multi_finish_reason = parse_completion_body(multi_response, raw: response) block.call(Chunk.new(role: :assistant, content: nil, tokens: .tokens, model: .model, citations: .citations, server_tool_calls: .server_tool_calls, tool_calls: .tool_calls, raw_content: .raw_content, attachments: ., finish_reason: .finish_reason)) end |