Module: Brute::Compaction::Transcript
- Defined in:
- lib/brute/compaction/transcript.rb
Overview
The shapes a compaction strategy selects over.
A transcript is an Array of Brute::Message, but what may be given up is never a single message. An assistant's tool calls travel with the results that answer them, and a user's question with everything said before the next one -- a provider rejects the halves. So a strategy works in groups: a step is an assistant message and the tool results immediately after it, a turn is a real user message and everything up to the next.
A message a strategy produced carries its name at the head of its content, because Brute::Message has nowhere else to put it. That is what stops a later pass from summarising a summary.
Constant Summary collapse
- MARK =
/\A\[compacted:([a-z_]+)\]/
Class Method Summary collapse
- .at(messages, indices) ⇒ Object
- .line(message) ⇒ Object
- .mark(strategy, text) ⇒ Object
- .marked?(message, strategy = nil) ⇒ Boolean
-
.render(messages) ⇒ Object
The transcript as plain text, for a summariser to read.
-
.step_start(messages) ⇒ Object
Where the current task's steps begin: after its anchor, or after the instructions when the conversation has no anchor at all.
-
.steps(messages, from:) ⇒ Object
An assistant message and every tool result answering it, as index ranges.
-
.system_end(messages) ⇒ Object
Where the leading system block ends.
-
.task_index(messages) ⇒ Object
The user message the current task hangs off, ignoring whatever an earlier compaction left behind.
-
.tokens(messages) ⇒ Object
Roughly what a slice costs, for code holding messages rather than an env.
-
.turns(messages, from:, to:) ⇒ Object
A real user message and everything up to the next one, as index ranges.
Class Method Details
.at(messages, indices) ⇒ Object
41 |
# File 'lib/brute/compaction/transcript.rb', line 41 def self.at(, indices) = indices.map { |index| [index] } |
.line(message) ⇒ Object
52 |
# File 'lib/brute/compaction/transcript.rb', line 52 def self.line() = Brute::TokenCounter::Rendering.() |
.mark(strategy, text) ⇒ Object
24 |
# File 'lib/brute/compaction/transcript.rb', line 24 def self.mark(strategy, text) = "[compacted:#{strategy}] #{text}" |
.marked?(message, strategy = nil) ⇒ Boolean
26 27 28 29 30 31 32 33 34 |
# File 'lib/brute/compaction/transcript.rb', line 26 def self.marked?(, strategy = nil) name = .content.to_s[MARK, 1] if strategy.nil? !name.nil? else name == strategy.to_s end end |
.render(messages) ⇒ Object
The transcript as plain text, for a summariser to read. Rendering it rather than replaying the messages keeps a half tool exchange -- a call whose result was left behind, a result whose call was -- off the wire, which is a shape providers refuse.
It is the same text the counters measure, so what a summariser is asked to shrink and what the trigger weighed are one thing.
50 |
# File 'lib/brute/compaction/transcript.rb', line 50 def self.render() = Brute::TokenCounter::Rendering.conversation() |
.step_start(messages) ⇒ Object
Where the current task's steps begin: after its anchor, or after the instructions when the conversation has no anchor at all.
67 68 69 70 71 72 73 74 75 |
# File 'lib/brute/compaction/transcript.rb', line 67 def self.step_start() task = task_index() if task.nil? system_end() else task + 1 end end |
.steps(messages, from:) ⇒ Object
An assistant message and every tool result answering it, as index ranges.
78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 |
# File 'lib/brute/compaction/transcript.rb', line 78 def self.steps(, from:) [].tap do |spans| index = from while index < .length if [index].role == :assistant finish = index + 1 while finish < .length && [finish].role == :tool finish += 1 end spans << (index...finish) index = finish else index += 1 end end end end |
.system_end(messages) ⇒ Object
Where the leading system block ends. Nothing above this is ever given up.
55 56 57 |
# File 'lib/brute/compaction/transcript.rb', line 55 def self.system_end() .index { || .role != :system } || .length end |
.task_index(messages) ⇒ Object
The user message the current task hangs off, ignoring whatever an earlier compaction left behind.
61 62 63 |
# File 'lib/brute/compaction/transcript.rb', line 61 def self.task_index() .rindex { || .role == :user && !marked?() } end |
.tokens(messages) ⇒ Object
Roughly what a slice costs, for code holding messages rather than an env. A strategy weighs with the turn's own counter instead -- see Brute::Compaction.counter.
39 |
# File 'lib/brute/compaction/transcript.rb', line 39 def self.tokens() = Brute::TokenCounter.default.count() |
.turns(messages, from:, to:) ⇒ Object
A real user message and everything up to the next one, as index ranges.
100 101 102 103 104 105 106 107 108 |
# File 'lib/brute/compaction/transcript.rb', line 100 def self.turns(, from:, to:) starts = (from...to).select do |index| [index].role == :user && !marked?([index]) end starts.each_with_index.map do |start, position| start...(starts[position + 1] || to) end end |