Module: Legion::LLM::Inference::ContextAccounting
- Defined in:
- lib/legion/llm/inference/context_accounting.rb
Constant Summary collapse
- TOKEN_CHAR_DIVISOR =
4- SCHEMA_VERSION =
1- TOKEN_KEYS =
%i[ request_message_estimated_tokens loaded_history_estimated_tokens curated_history_estimated_tokens curation_saved_estimated_tokens stripped_thinking_estimated_tokens archived_history_estimated_tokens archive_saved_estimated_tokens context_window_saved_estimated_tokens rag_injected_estimated_tokens system_prompt_estimated_tokens baseline_system_estimated_tokens tool_definition_estimated_tokens final_context_estimated_tokens ].freeze
- COUNT_KEYS =
%i[ loaded_history_message_count curated_history_message_count archived_history_message_count stripped_thinking_message_count context_window_message_count_before context_window_message_count_after rag_entry_count tool_definition_count ].freeze
- CONTEXT_ACCOUNTING_STATUS_RANK =
{ 'missing' => 0, 'profile_skipped' => 1, 'partial' => 2, 'estimated' => 3, 'provider_reconciled' => 4 }.freeze
Class Method Summary collapse
- .content_text(content) ⇒ Object
- .empty(status: :estimated) ⇒ Object
- .estimate_json_tokens(value) ⇒ Object
- .estimate_message_tokens(messages) ⇒ Object
- .estimate_text_tokens(text) ⇒ Object
- .event(event_type:, component:, before_tokens:, after_tokens:, before_count: 0, after_count: 0, metadata: {}) ⇒ Object
-
.message_text(message) ⇒ Object
Extract plain text from a message for token estimation.
- .part_text(part) ⇒ Object
Class Method Details
.content_text(content) ⇒ Object
96 97 98 99 100 101 102 103 |
# File 'lib/legion/llm/inference/context_accounting.rb', line 96 def content_text(content) case content when nil then '' when String then content when Array then content.map { |part| part_text(part) }.join("\n") else part_text(content) end end |
.empty(status: :estimated) ⇒ Object
47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
# File 'lib/legion/llm/inference/context_accounting.rb', line 47 def empty(status: :estimated) { schema_version: SCHEMA_VERSION, status: status, estimator: { name: :legion_char_div_four, version: 1 }, counts: COUNT_KEYS.to_h { |key| [key, 0] }, tokens: TOKEN_KEYS.to_h { |key| [key, 0] }, reconciliation: {}, component_status: { context_load: :not_observed, curation: :not_observed, archive: :not_observed, rag: :not_observed, tools: :not_observed, system: :not_observed, context_window: :not_observed, thinking_strip: :not_observed }, events: [] } end |
.estimate_json_tokens(value) ⇒ Object
77 78 79 |
# File 'lib/legion/llm/inference/context_accounting.rb', line 77 def estimate_json_tokens(value) estimate_text_tokens(Legion::JSON.dump(value || {})) end |
.estimate_message_tokens(messages) ⇒ Object
73 74 75 |
# File 'lib/legion/llm/inference/context_accounting.rb', line 73 def () Array().sum { || estimate_text_tokens(()) } end |
.estimate_text_tokens(text) ⇒ Object
69 70 71 |
# File 'lib/legion/llm/inference/context_accounting.rb', line 69 def estimate_text_tokens(text) (text.to_s.length / TOKEN_CHAR_DIVISOR.to_f).ceil end |
.event(event_type:, component:, before_tokens:, after_tokens:, before_count: 0, after_count: 0, metadata: {}) ⇒ Object
116 117 118 119 120 121 122 123 124 125 126 127 |
# File 'lib/legion/llm/inference/context_accounting.rb', line 116 def event(event_type:, component:, before_tokens:, after_tokens:, before_count: 0, after_count: 0, metadata: {}) { event_type: event_type, component: component, estimated_tokens_before: before_tokens.to_i, estimated_tokens_after: after_tokens.to_i, estimated_tokens_delta: after_tokens.to_i - before_tokens.to_i, message_count_before: before_count.to_i, message_count_after: after_count.to_i, metadata: } end |
.message_text(message) ⇒ Object
Extract plain text from a message for token estimation.
IMPORTANT: Canonical::Message is a Ruby data struct; #to_s returns the struct’s #inspect dump (timestamps, nested ContentBlocks, tool_calls, etc.) — counting that as “text” inflates a hello-world payload to ~3.4MB → ~854K char-div-4 “tokens” and breaks Router.request_lane’s context-window filter. Use the canonical #text accessor.
88 89 90 91 92 93 94 |
# File 'lib/legion/llm/inference/context_accounting.rb', line 88 def () return if .is_a?(String) return content_text([:content]) if .is_a?(Hash) return .text.to_s if .respond_to?(:text) '' end |
.part_text(part) ⇒ Object
105 106 107 108 109 110 111 112 113 114 |
# File 'lib/legion/llm/inference/context_accounting.rb', line 105 def part_text(part) return part if part.is_a?(String) return (part[:text] || content_text(part[:content])).to_s if part.is_a?(Hash) # Canonical::ContentBlock — only text/thinking blocks carry textual content; # tool_use / tool_result / image have nil or non-text payloads. return part.text.to_s if part.respond_to?(:text?) && part.text? return part.text.to_s if part.respond_to?(:text) && !part.respond_to?(:text?) '' end |