Module: Mistri::Providers::OpenAI::Serializer
- Defined in:
- lib/mistri/providers/openai/serializer.rb
Overview
Serializes protocol messages into Responses API input items.
Replay pairing is the rule that matters: with store: false the full history resends every turn, and a reasoning item must return VERBATIM (its encrypted_content is the model's chain of thought) followed by the same items it originally preceded. The signature slots carry what pairing needs: Thinking.signature holds the whole reasoning item, Text.signature the message item id, ToolCall.signature the function_call item id.
Class Method Summary collapse
- .assistant_item(block) ⇒ Object
- .assistant_items(msg) ⇒ Object
- .function_call_item(block) ⇒ Object
- .input_items(history) ⇒ Object
- .items_for(msg) ⇒ Object
- .message_item(block) ⇒ Object
-
.reasoning_item(block) ⇒ Object
The reasoning item replays exactly as it arrived or not at all.
-
.tool_result_item(msg) ⇒ Object
Non-text blocks in a tool result have no function_call_output encoding; note the omission rather than dropping it silently.
- .tools(definitions) ⇒ Object
- .user_block(block) ⇒ Object
- .user_item(msg) ⇒ Object
Class Method Details
.assistant_item(block) ⇒ Object
68 69 70 71 72 73 74 |
# File 'lib/mistri/providers/openai/serializer.rb', line 68 def assistant_item(block) case block when Content::Thinking then reasoning_item(block) when Content::Text then (block) when ToolCall then function_call_item(block) end end |
.assistant_items(msg) ⇒ Object
64 65 66 |
# File 'lib/mistri/providers/openai/serializer.rb', line 64 def assistant_items(msg) msg.content.filter_map { |block| assistant_item(block) } end |
.function_call_item(block) ⇒ Object
95 96 97 98 99 100 |
# File 'lib/mistri/providers/openai/serializer.rb', line 95 def function_call_item(block) item = { type: "function_call", call_id: block.id, name: block.name, arguments: JSON.generate(block.arguments) } item[:id] = block.signature if block.signature item end |
.input_items(history) ⇒ Object
20 21 22 |
# File 'lib/mistri/providers/openai/serializer.rb', line 20 def input_items(history) history.reject(&:system?).flat_map { |msg| items_for(msg) } end |
.items_for(msg) ⇒ Object
32 33 34 35 36 37 38 39 |
# File 'lib/mistri/providers/openai/serializer.rb', line 32 def items_for(msg) case msg.role when :user then [user_item(msg)] when :tool then [tool_result_item(msg)] when :assistant then assistant_items(msg) else [] end end |
.message_item(block) ⇒ Object
88 89 90 91 92 93 |
# File 'lib/mistri/providers/openai/serializer.rb', line 88 def (block) item = { type: "message", role: "assistant", content: [{ type: "output_text", text: block.text }] } item[:id] = block.signature if block.signature item end |
.reasoning_item(block) ⇒ Object
The reasoning item replays exactly as it arrived or not at all. An item without encrypted_content triggers a server-side id lookup that cannot succeed under store: false, so it drops rather than 404s.
79 80 81 82 83 84 85 86 |
# File 'lib/mistri/providers/openai/serializer.rb', line 79 def reasoning_item(block) return nil unless block.signature item = JSON.parse(block.signature) item.is_a?(Hash) && item["encrypted_content"] ? item : nil rescue JSON::ParserError nil end |
.tool_result_item(msg) ⇒ Object
Non-text blocks in a tool result have no function_call_output encoding; note the omission rather than dropping it silently.
57 58 59 60 61 62 |
# File 'lib/mistri/providers/openai/serializer.rb', line 57 def tool_result_item(msg) omitted = msg.content.count { |block| !block.is_a?(Content::Text) } text = msg.text.to_s text = "#{text}\n[#{omitted} non-text block(s) omitted]".strip if omitted.positive? { type: "function_call_output", call_id: msg.tool_call_id, output: text } end |
.tools(definitions) ⇒ Object
24 25 26 27 28 29 30 |
# File 'lib/mistri/providers/openai/serializer.rb', line 24 def tools(definitions) definitions.map do |tool| spec = tool.transform_keys(&:to_sym) { type: "function", name: spec[:name], description: spec[:description], parameters: spec[:input_schema] } end end |
.user_block(block) ⇒ Object
45 46 47 48 49 50 51 52 53 |
# File 'lib/mistri/providers/openai/serializer.rb', line 45 def user_block(block) case block when Content::Text then { type: "input_text", text: block.text } when Content::Image { type: "input_image", image_url: "data:#{block.mime_type};base64,#{block.data}" } else raise SchemaError, "cannot serialize #{block.class} for OpenAI user input" end end |
.user_item(msg) ⇒ Object
41 42 43 |
# File 'lib/mistri/providers/openai/serializer.rb', line 41 def user_item(msg) { role: "user", content: msg.content.map { |block| user_block(block) } } end |