Class: Rubino::LLM::AdapterResponse

Inherits:
Object
  • Object
show all
Defined in:
lib/rubino/llm/adapter_response.rb

Overview

Structured response returned by all LLM adapters — the normalized shape the conversation loop and its recovery layers read, never ruby_llm internals. This is the Ruby side of the reference normalize_response seam: the loop branches only on content / thinking / tool_calls / stop_reason / interrupted?, never on provider types.

All recovery-layer fields (thinking, stop_reason, usage, raw) default nil-safely so existing callers that construct only the core fields keep working unchanged.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(content:, tool_calls:, input_tokens:, output_tokens:, model_id:, interrupted: false, thinking: nil, stop_reason: nil, raw: nil, cache_read_tokens: 0, cache_creation_tokens: 0, halted: false, final_text_block: nil) ⇒ AdapterResponse

Returns a new instance of AdapterResponse.



18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# File 'lib/rubino/llm/adapter_response.rb', line 18

def initialize(content:, tool_calls:, input_tokens:, output_tokens:, model_id:,
               interrupted: false, thinking: nil, stop_reason: nil, raw: nil,
               cache_read_tokens: 0, cache_creation_tokens: 0, halted: false,
               final_text_block: nil)
  @content = content
  # The LAST assistant text block of the turn, in isolation — the answer text
  # the model emitted AFTER its final tool call, with no earlier pre-tool
  # narration glued on (#core-F1). `content` keeps EVERY text block of the
  # turn concatenated (needed for the transcript and on-screen render); this
  # field is what a headless one-shot `result` should surface, so
  # `OUT=$(rubino prompt …)` returns just the answer and not
  # "I'll do X now.<answer>". Falls back to `content` when the adapter did not
  # track block boundaries (non-streaming / Halt / test doubles), where the
  # response already carries only the final block.
  @final_text_block = final_text_block
  @tool_calls    = tool_calls || []
  @input_tokens  = input_tokens || 0
  @output_tokens = output_tokens || 0
  @model_id      = model_id
  # Prompt-cache usage surfaced by the provider (#311). cache_read_tokens
  # > 0 means the cached prefix/tool-block was reused on this request;
  # cache_creation_tokens > 0 means it was (re)written. Default 0 so every
  # existing caller / provider path that omits them is unaffected.
  @cache_read_tokens     = cache_read_tokens || 0
  @cache_creation_tokens = cache_creation_tokens || 0
  # True when this response holds only a buffered partial from a stream that
  # was cut before a clean completion (no finish_reason / [DONE]). The Loop
  # must treat it as a turn failure, never as a final answer.
  @interrupted   = interrupted
  # Reasoning text/summary if the provider surfaced it (think blocks are
  # already split out of +content+). nil when not surfaced on this path.
  @thinking      = thinking
  # Normalized finish reason: :stop | :length | :tool_calls | nil. Drives
  # truncation continuation (later slice). Left nil where unreachable —
  # never fabricated.
  @stop_reason   = stop_reason
  # Escape hatch to the underlying provider response. The loop must NOT
  # branch on it; it exists for diagnostics / later-slice needs only.
  @raw           = raw
  # True when the streaming round-trip loop was HALTED mid-flight because
  # the per-turn iteration/time budget was exhausted (#355a). The Loop reads
  # this to run its budget-exhausted summary instead of treating the
  # buffered preamble as the final answer.
  @halted        = halted
end

Instance Attribute Details

#cache_creation_tokensObject (readonly)

Returns the value of attribute cache_creation_tokens.



15
16
17
# File 'lib/rubino/llm/adapter_response.rb', line 15

def cache_creation_tokens
  @cache_creation_tokens
end

#cache_read_tokensObject (readonly)

Returns the value of attribute cache_read_tokens.



15
16
17
# File 'lib/rubino/llm/adapter_response.rb', line 15

def cache_read_tokens
  @cache_read_tokens
end

#contentObject (readonly)

Returns the value of attribute content.



15
16
17
# File 'lib/rubino/llm/adapter_response.rb', line 15

def content
  @content
end

#input_tokensObject (readonly)

Returns the value of attribute input_tokens.



15
16
17
# File 'lib/rubino/llm/adapter_response.rb', line 15

def input_tokens
  @input_tokens
end

#model_idObject (readonly)

Returns the value of attribute model_id.



15
16
17
# File 'lib/rubino/llm/adapter_response.rb', line 15

def model_id
  @model_id
end

#output_tokensObject (readonly)

Returns the value of attribute output_tokens.



15
16
17
# File 'lib/rubino/llm/adapter_response.rb', line 15

def output_tokens
  @output_tokens
end

#rawObject (readonly)

Returns the value of attribute raw.



15
16
17
# File 'lib/rubino/llm/adapter_response.rb', line 15

def raw
  @raw
end

#stop_reasonObject (readonly)

Returns the value of attribute stop_reason.



15
16
17
# File 'lib/rubino/llm/adapter_response.rb', line 15

def stop_reason
  @stop_reason
end

#thinkingObject (readonly)

Returns the value of attribute thinking.



15
16
17
# File 'lib/rubino/llm/adapter_response.rb', line 15

def thinking
  @thinking
end

#tool_callsObject (readonly)

Returns the value of attribute tool_calls.



15
16
17
# File 'lib/rubino/llm/adapter_response.rb', line 15

def tool_calls
  @tool_calls
end

Instance Method Details

#final_text_blockObject

The final assistant text block in isolation (see #initialize). Used by the headless one-shot answer path so a turn that ended with a tool call returns only the post-tool answer, not the pre-tool narration concatenated in content. Falls back to content when no per-block boundary was tracked.



99
100
101
# File 'lib/rubino/llm/adapter_response.rb', line 99

def final_text_block
  @final_text_block.nil? ? @content : @final_text_block
end

#halted?Boolean

See #initialize — the streaming tool loop was cut short by the budget.

Returns:

  • (Boolean)


65
66
67
# File 'lib/rubino/llm/adapter_response.rb', line 65

def halted?
  @halted
end

#has_tool_calls?Boolean

Returns:

  • (Boolean)


87
88
89
# File 'lib/rubino/llm/adapter_response.rb', line 87

def has_tool_calls?
  !@tool_calls.empty?
end

#interrupted?Boolean

The stream was truncated; content is an incomplete partial, not a finished turn. See AdapterResponse#initialize and Loop#run.

Returns:

  • (Boolean)


83
84
85
# File 'lib/rubino/llm/adapter_response.rb', line 83

def interrupted?
  @interrupted
end

#text_only?Boolean

Returns:

  • (Boolean)


91
92
93
# File 'lib/rubino/llm/adapter_response.rb', line 91

def text_only?
  !has_tool_calls? && !@content.nil? && !@content.empty?
end

#total_tokensObject



103
104
105
# File 'lib/rubino/llm/adapter_response.rb', line 103

def total_tokens
  @input_tokens + @output_tokens
end

#usageObject

Token usage as a nil-safe Hash, the shape the recovery layers read. Carries the prompt-cache counters (#311) so a caller can confirm a cache hit (cache_read_input_tokens > 0) without reaching into the raw body.



72
73
74
75
76
77
78
79
# File 'lib/rubino/llm/adapter_response.rb', line 72

def usage
  {
    input_tokens: @input_tokens,
    output_tokens: @output_tokens,
    cache_read_input_tokens: @cache_read_tokens,
    cache_creation_input_tokens: @cache_creation_tokens
  }
end