Module: Mistri::Providers::Gemini::Serializer
- Defined in:
- lib/mistri/providers/gemini/serializer.rb
Overview
Serializes protocol messages into generateContent contents.
Wire rules that matter: roles are user and model, consecutive tool results merge into one user turn of functionResponse parts, and thought signatures echo back verbatim on the exact part they arrived with, but only for messages this provider produced; a foreign signature would be rejected. Thinking summaries are output-only and never replay. Consecutive user turns stay separate: Gemini accepts them, and mixing a text part into a functionResponse turn makes it answer an empty candidate.
Class Method Summary collapse
- .assistant_parts(msg) ⇒ Object
- .contents(history) ⇒ Object
-
.result_text(msg) ⇒ Object
Non-text blocks in a tool result have no functionResponse encoding; note the omission rather than dropping it silently.
- .signed(part, signature, own) ⇒ Object
- .system_instruction(system) ⇒ Object
-
.tool_turn(group) ⇒ Object
Gemini pairs a functionResponse to its call by NAME; a wrong name silently mismatches, so a missing one fails loudly instead.
- .tools(definitions) ⇒ Object
- .turn(msg) ⇒ Object
- .user_parts(msg) ⇒ Object
Class Method Details
.assistant_parts(msg) ⇒ Object
81 82 83 84 85 86 87 88 89 90 91 |
# File 'lib/mistri/providers/gemini/serializer.rb', line 81 def assistant_parts(msg) own = msg.provider == :gemini msg.content.filter_map do |block| case block when Content::Text then signed({ text: block.text }, block.signature, own) when ToolCall signed({ functionCall: { name: block.name, args: block.arguments } }, block.signature, own) end end end |
.contents(history) ⇒ Object
19 20 21 22 23 24 |
# File 'lib/mistri/providers/gemini/serializer.rb', line 19 def contents(history) groups = history.reject(&:system?).chunk_while { |a, b| a.tool? && b.tool? } groups.filter_map do |group| group.first.tool? ? tool_turn(group) : turn(group.first) end end |
.result_text(msg) ⇒ Object
Non-text blocks in a tool result have no functionResponse encoding; note the omission rather than dropping it silently.
63 64 65 66 67 |
# File 'lib/mistri/providers/gemini/serializer.rb', line 63 def result_text(msg) omitted = msg.content.count { |block| !block.is_a?(Content::Text) } text = msg.text.to_s omitted.positive? ? "#{text}\n[#{omitted} non-text block(s) omitted]".strip : text end |
.signed(part, signature, own) ⇒ Object
93 94 95 96 |
# File 'lib/mistri/providers/gemini/serializer.rb', line 93 def signed(part, signature, own) part[:thoughtSignature] = signature if own && signature part end |
.system_instruction(system) ⇒ Object
26 27 28 29 30 |
# File 'lib/mistri/providers/gemini/serializer.rb', line 26 def system_instruction(system) return nil if system.nil? || system.empty? { parts: [{ text: system }] } end |
.tool_turn(group) ⇒ Object
Gemini pairs a functionResponse to its call by NAME; a wrong name silently mismatches, so a missing one fails loudly instead.
50 51 52 53 54 55 56 57 58 59 |
# File 'lib/mistri/providers/gemini/serializer.rb', line 50 def tool_turn(group) { role: "user", parts: group.map do |msg| unless msg.tool_name raise SchemaError, "Gemini tool results need tool_name to pair with their call" end { functionResponse: { name: msg.tool_name, response: { "result" => result_text(msg) } } } end } end |
.tools(definitions) ⇒ Object
32 33 34 35 36 37 38 39 |
# File 'lib/mistri/providers/gemini/serializer.rb', line 32 def tools(definitions) declarations = definitions.map do |tool| spec = tool.transform_keys(&:to_sym) { name: spec[:name], description: spec[:description], parameters: spec[:input_schema] } end [{ functionDeclarations: declarations }] end |
.turn(msg) ⇒ Object
41 42 43 44 45 46 |
# File 'lib/mistri/providers/gemini/serializer.rb', line 41 def turn(msg) parts = msg.assistant? ? assistant_parts(msg) : user_parts(msg) return nil if parts.empty? { role: msg.assistant? ? "model" : "user", parts: parts } end |
.user_parts(msg) ⇒ Object
69 70 71 72 73 74 75 76 77 78 79 |
# File 'lib/mistri/providers/gemini/serializer.rb', line 69 def user_parts(msg) msg.content.map do |block| case block when Content::Text then { text: block.text } when Content::Image { inlineData: { mimeType: block.mime_type, data: block.data } } else raise SchemaError, "cannot serialize #{block.class} for Gemini user input" end end end |